diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index f1bca0e8..0ca4927b 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -16,6 +16,23 @@ --- +## 2026-06-03 拼消消收敛为单关 6x6 与 4-sheet 素材策略 + +- 背景:最初 4 关 / 135 次消除 / 单张大 atlas 方案生图数量和空间一致性成本过高,真实 image2 结果容易被布局提示词诱导成带文字、边框或编号的说明图,不适合运行态 1x1 切片。 +- 决策:拼消消运行态收敛为单关 `6x6 / 35 次消除 / 600 秒`,直接解锁 `1x2`、`1x3`、`2x2`、`2x3`;素材生成改为 4 张 `1024x1536` 竖版 sheet,每张按 `4x6`、每格 `256x256` 切片,再由服务端合成 `10x10 / 2560x2560` 最终 atlas。形状配比固定为 `1x2=23`、`1x3=5`、`2x2=4`、`2x3=3`,总计 35 个复合图案组和 95 个 1x1 卡牌切片。 +- 影响范围:`module-puzzle-clear` 关卡与图案组规划、api-server 拼消消素材生成编排、前端草稿试玩本地 runtime、结果页 atlas 预览、拼消消 PRD / 技术方案 / 平台链路文档。 +- 验证方式:`cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle_clear --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts`、`npm run test -- src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 +- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。 + +## 2026-05-30 拼消消按独立玩法公开闭环接入 + +- 背景:拼消消以拼图交换手感为基础,但核心规则从“拼完整单图过关”变为“拼成多个复合图案组后逐个消除”,同时需要顶部补牌、防死局、半锁定局部拼接组和正式统计,不能继续复用拼图运行态规则本体。 +- 决策:`puzzle-clear` 作为独立玩法域接入,公开作品码前缀固定为 `PC-`;创作链路采用表单 / 图片输入工作台 -> 独立生成页 -> 结果页 -> 试玩 -> 发布 -> 统一作品详情 -> 正式 runtime。领域规则落在 `module-puzzle-clear`,SpacetimeDB 新增 `puzzle_clear_*` 表 / procedure / view,并接入统一 `public_work_gallery_entry` / `public_work_detail_entry`;前端只表现后端 snapshot/action 结果,不把胜负、补牌或消除裁决做成前端事实源。 +- 补充约束:草稿编译和发布都必须拒绝缺失或 `placeholder` atlas / card assets,不允许后端 facade 或 SpacetimeDB 合成临时素材;当前单关正式 runtime 终态事件使用 `run-finished`、`level-failed`,并写入包含 `status`、`level`、`clears`、`clearDelta`、`elapsedMs` 的结果 JSON。 +- 补充约束:拼消消结果页草稿试玩使用前端本地 `runtimeMode=draft` snapshot,不调用 `/api/runtime/puzzle-clear/runs`,不写正式 run 统计;公开详情和推荐流正式运行继续走后端 `/api/runtime/puzzle-clear/*`,客户端需要区分创作详情 `/api/creation/puzzle-clear/works/{profileId}` 与公开运行态详情 `/api/runtime/puzzle-clear/works/{profileId}`。 +- 影响范围:`CONTEXT.md`、拼消消 PRD / 技术方案、平台玩法链路文档、`shared-contracts` / `packages/shared`、`api-server`、`spacetime-module`、`spacetime-client`、作品架 / 广场 / 统一作品详情 / runtime 前端分流。 +- 验证方式:PRD 和技术方案必须覆盖资产槽位、素材工作表风险、切片验证、恢复语义、API 命名空间和验证命令;实现侧至少运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`npm run check:server-rs-ddd`、`npm run typecheck`、`npm run check:encoding`、相关前端测试和 `cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml`。 +- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。 ## 2026-06-05 Server-Provision 全程在目标部署 agent 执行且不安装构建链 - 背景:`Genarrative-Server-Provision` 的 `DEPLOY_TARGET=development` 语义是部署到 dev 服务器,不是构建机 dry-run。旧流水线把 development 映射到 `linux && genarrative-build`,还先在 build 节点准备 `provision-tools/` 再 stash 给后续阶段,导致真实 dev 初始化可能跑到 Jenkins controller / build 节点;脚本还安装 clang / lld / pkg-config / OpenSSL headers / sccache 等构建链依赖,超出了服务器初始化职责。 @@ -1280,3 +1297,11 @@ - 影响范围:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/module-auth/src/lib.rs`、`server-rs/crates/spacetime-module/src/auth/procedures.rs`、`server-rs/crates/spacetime-client/src/auth.rs`、对应生成 bindings。 - 验证方式:`cargo check -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth password --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run check:spacetime-schema`、`npm run check:encoding`、`cargo test -p api-server spacetime_unavailable_router_returns_service_unavailable_for_requests --manifest-path server-rs/Cargo.toml -- --nocapture`。 - 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 + +## 2026-05-31 拼消消底图 prompt 与 atlas 切片提示词收口 + +- 背景:拼消消生成资产检查时,用户需要区分主题词、场地底图主题词和复合图 atlas prompt 的职责;若小图案显式画出切分线或边框,运行态 1x1 切片会显得像错误素材。 +- 决策:`boardBackgroundPrompt` 成为中央场地底图的优先 prompt 来源,只有该字段为空时才回退读取 `themePrompt`;用户上传底图时只执行平台资产持久化和换签,不用主题词重写上传资产。复合图 atlas prompt 只描述“可被服务端按等大 1x1 方格切分”,禁止模型在图案上绘制切分线、边框、网格线或裁切参考线。 +- 影响范围:拼消消工作台 payload、`shared-contracts` / `packages/shared` 契约、api-server 生成编排、SpacetimeDB session/work snapshot、文档与生成进度展示。 +- 验证方式:`npm run spacetime:generate`、`npm run check:encoding`、`npm run check:server-rs-ddd`、`cargo test -p module-puzzle-clear`、`cargo test -p spacetime-client puzzle_clear -- --nocapture`、`npm run test -- src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx src/services/miniGameDraftGenerationProgress.test.ts src/routing/appPageRoutes.test.ts src/services/publicWorkCode.test.ts`。 +- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index feb5b666..ebdffc89 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -1,4 +1,4 @@ -# 踩坑与排障记录 +# 踩坑与排障记录 > 用途:记录已验证、未来很可能再次遇到的问题。每条都应包含现象、原因、处理方式和验证方式。 @@ -48,12 +48,12 @@ - 验证:`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_image_edit_retries_send_timeout_once_and_succeeds`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`;查询 `tracking_event` 时失败记录应能看到触发者 `user_id` 和可用的 `profile_id`。 - 关联:`server-rs/crates/platform-image/src/vector_engine/client.rs`、`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 -## “我的”页每日任务卡不要硬编码进度 +## “我的”页每日任务卡不要硬编码进度,也不要跨日保留旧状态 - 现象:用户完成或领取每日任务后,任务中心弹窗里的任务状态已经变化,但“我的”页卡片仍显示 `0 / 1` 和“去完成”。 -- 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗。 -- 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心。 -- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`,领取后显示已完成。 +- 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗;后来虽然后端按北京时间 0 点切换业务日,但前端停留在“我的”页时不会跨日刷新,可能继续展示上一日已领取状态。 +- 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心;停留在“我的”页跨过北京时间 0 点时,先非阻断 refresh 登录态写入新业务日 `daily_login`,再重拉任务中心。 +- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`、领取后显示已完成,以及北京时间 0 点自动 refresh 后重拉任务中心。 - 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。 ## “我的”页不要恢复旧的填邀请码次级按钮 @@ -104,6 +104,30 @@ - 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t \"puzzle draft generation auto starts trial and runtime back opens draft result\"`,确认 `window.location.pathname === '/runtime/puzzle'` 且 `window.location.search` 同时包含 `runtimeProfileId` 和 `runtimeSessionId`。 - 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/services/puzzleRuntimeUrlState.ts`、`src/routing/appPageRoutes.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 拼消消草稿试玩不能只测 swap 回调 + +- 现象:拼消消结果页和 runtime shell 的单测都能通过,但真实页面里卡片只是交换,完全不会消除,顶部准备区还会因为已知的卡背占位路径显示坏图。 +- 原因:草稿试玩走的是前端本地 runtime,早期测试只覆盖了 `onSwapCards` 回调和局部状态,没有验证完整的消除、重力补牌、关卡完成和资源兜底链路;同时顶部卡背对 `puzzle-clear-card-back.webp` 这类已知缺失资源没有前置回退。 +- 处理:草稿试玩的回归测试必须覆盖“交换 -> 完整图案消除 -> 补牌 -> 关卡完成”闭环,并在组件测试里验证真实点击/拖拽序列;顶部准备区卡背遇到已知占位路径时直接回退到 `puzzle.webp` 这类可用参考图,不等图片加载失败后再兜底。 +- 验证:`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx` 通过,浏览器 smoke 页实测可完成一次消除并弹出“本关完成”。 +- 关联:`src/services/puzzle-clear/puzzleClearLocalRuntime.ts`、`src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 + +## 拼消消消除过渡不能隐藏已有卡片的最终下沉格 + +- 现象:消除补牌过程中偶尔看起来下方有空位,但同列上方卡片没有落下来。 +- 原因:后端和本地 runtime 的重力补牌已经把已有卡片压到底;真正的问题在前端过渡层。消除动画曾按旧消除坐标隐藏棋盘格,掉落动画也曾隐藏所有 drop 目标格。当某个旧卡下沉到刚被消除的格子时,最终 snapshot 里的真实卡片会被隐藏,视觉上像补牌没有落下。 +- 处理:消除 / 掉落覆盖层只负责动画表现,不再隐藏已有场上卡片的最终格;只有从顶部准备区新补入、前一帧棋盘不存在的卡片,才允许临时隐藏底层目标格来配合下落动画。 +- 验证:`npm run test -- src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx -t "已有卡片因重力下沉时目标格不被过渡状态隐藏成空位"`,并保留领域侧 `cargo test -p module-puzzle-clear refill --manifest-path server-rs/Cargo.toml`。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`server-rs/crates/module-puzzle-clear/src/application.rs`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`。 + +## 拼消消完整消除反馈不要让补牌抢帧 + +- 现象:玩家正确拼完整组后,卡片几乎瞬间消失,顶部补牌马上出现或下落,导致“拼对了”的确认反馈很弱。 +- 原因:前端一收到新 snapshot 就同时播放消除和掉落叠层,旧消除动画时长较短;新补入卡牌的下落延迟接近 0ms,视觉上会抢在消除反馈之前开始。 +- 处理:局部正确拼合但未消除时只给锁定组做一次高光;完整消除时让旧卡片在消除叠层中短暂放大展示再淡出;新补入卡牌的下落延迟到淡出尾段,并继续只隐藏新补入目标格,不隐藏已有场上卡片下沉后的最终格。 +- 验证:`npm run test -- src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`,浏览器里确认局部拼合会闪、完整消除会放大淡出、补牌在淡出后段才开始掉落。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/index.css`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 + ## 首页推荐分流参数不能条件性调用 hook - 现象:桌面首页或移动首页在 HMR、断点切换或重新渲染后直接报 React hook 顺序错误,页面停在“正在加载内容”。 @@ -135,6 +159,14 @@ - 验证:后台保存两条以上公告后,点击底部加号进入创作入口页应自动轮播这些后台配置项;`CustomWorldCreationHub` 相关测试应断言标题来自后端配置。 - 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`server-rs/crates/module-runtime/src/application.rs`、`apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx`。 +## 创作入口 banner 默认图片路径必须真实存在 + +- 现象:创作页顶部 banner 返回旧结构化 `eventBanner` 时,前端 `` 请求 `/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png`,但 `public/` 下没有该文件,导致 banner 背景图加载失败。 +- 原因:旧库 `event_banners_json=None` 时,读取层把旧单条结构化 banner 当成 `eventBanners` 优先数组下发;同时旧结构化默认 `coverImageSrc` 指向已经不存在的品牌素材路径。 +- 处理:`module-runtime` 在 `event_banners_json` 缺失或不可解析时回到默认公告数组;默认 HTML 公告和旧结构化默认 `coverImageSrc` 都引用 `public/` 下真实存在的 `/creation-type-references/puzzle.webp`。 +- 验证:`cargo test -p module-runtime creation_entry_event_banners_none_returns_default_announcements --manifest-path server-rs/Cargo.toml`;重启本地 `api-server` 后 `GET /api/creation-entry/config` 的 `eventBanners[0]` 不再指向缺失的 `/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png`。 +- 关联:`server-rs/crates/module-runtime/src/application.rs`、`server-rs/crates/module-runtime/src/domain.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + ## 移动端草稿卡不要长按选中文字 - 现象:移动端草稿页长按作品卡标题或摘要时触发系统文字选区,容易误触并打断作品架操作。 @@ -1018,6 +1050,14 @@ - 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`。 - 关联:`server-rs/crates/module-auth/src/errors.rs`、`server-rs/crates/api-server/src/phone_auth.rs`、`docs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md`。 + +## 本地短信 smoke 先确认 SMS provider + +- 现象:浏览器里短信验证码发送成功,但提交 `123456` 仍然报验证码错误,或者短信登录后又回到未登录态。 +- 原因:当前运行中的 `api-server` 如果读取到 `.env.local` 里的 `SMS_AUTH_PROVIDER=aliyun`,就会走真实短信 provider 口径;这时 mock 验证码 `123456` 不会被接受。之前本地调试时常见的误判是把 `.env.local` 改成 mock 了,但没有重启 `npm run dev`,或者旧的 `scripts/dev.mjs` 进程还在沿用旧环境。 +- 处理:本地只做 UI / 账号链路 smoke 时,把 `.env.local` 显式设为 `SMS_AUTH_PROVIDER=mock` 且配置 `SMS_AUTH_MOCK_VERIFY_CODE=123456`,然后重启 `npm run dev` 或 `npm run dev:api-server`。要做真实短信联调时,再切回 `SMS_AUTH_PROVIDER=aliyun` 并重启。 +- 验证:`POST /api/auth/phone/send-code` 应返回 `providerRequestId=mock-request-id`;`POST /api/auth/phone/login` 用 `123456` 应返回 `200` 且 `user.loginMethod=phone`。浏览器侧短信登录成功后,会先进入邀请码弹窗或我的页面,不应再提示“验证码错误”。 +- 关联:`scripts/dev-utils.mjs`、`scripts/dev-utils.test.ts`、`scripts/dev.mjs`、`server-rs/crates/api-server/src/config.rs`。 ## 手机验证码登录成功后又瞬间回到未登录 - 现象:手机号验证码登录先成功,随后 UI 又闪回“未登录”,登录弹窗可能重新出现。 @@ -1601,10 +1641,18 @@ - 现象:拼图生成页已经收到 VectorEngine 图片编辑失败并进入重试态,但用户返回草稿 Tab 后,同一草稿仍显示“生成中”;连续触发多个拼图生成时,失败后还可能只剩一条新增草稿,或者只看到标题为“第1关”的半成品空壳;抓大鹅后台失败时也可能没有任何通知,点击草稿又像重新开始生成。 - 原因:前端失败 notice 只更新生成页局部状态,pending 作品架条目在失败时被清掉或被非 `generating` 状态误映射为 `ready`;后端作品摘要也可能短暂仍是 `generationStatus=generating`。如果失败消息没有写入 notice,用户离开生成页后不会弹出 `PlatformErrorDialog`;如果打开草稿只看持久化 `generating`,就会绕过失败态恢复。 -- 处理:失败时按 session 保留 pending 作品架条目并标记 `failed`,失败 notice 保存错误消息并触发带来源的 `PlatformErrorDialog`;拼图契约没有 `failed` 枚举,pending 拼图映射为 `idle`,同时用本地失败 notice 覆盖持久化生成中状态和旧的“正在生成”摘要。点击失败草稿应优先用 notice / 后端 session / fallback payload 组装失败生成页,不能重新从 0 秒启动新进度;拼图失败半成品没有有效 `workTitle` 时,作品架标题回退为“拼图草稿”。 +- 处理:失败时按 session 保留 pending 作品架条目并标记 `failed`,失败 notice 保存错误消息并触发带来源的 `PlatformErrorDialog`;拼图契约没有 `failed` 枚举,pending 拼图映射为 `idle`,同时用本地失败 notice 覆盖持久化生成中状态和旧的“正在生成”摘要。点击失败草稿应优先用 notice / 后端 session / fallback payload 组装失败生成页,不能重新从 0 秒启动新进度;失败页点击重新生成必须优先复用当前 `sessionId` 执行编译 action,不得因存在表单缓存 payload 就调用 create-session。拼图失败半成品没有有效 `workTitle` 时,作品架标题回退为“拼图草稿”。 - 验证:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed parallel puzzle|background match3d"`。 - 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/custom-world-home/creationWorkShelf.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 生成失败重试不要走新建草稿 + +- 现象:拼图或抓大鹅生成失败后,在失败页点击“重新生成”,作品架里多出一份新的草稿,原失败草稿仍留在列表里。 +- 原因:重试 handler 曾优先读取缓存的表单 payload 并调用 create-session 路径;失败草稿按 session 留在作品架是正确行为,于是重试动作额外创建了第二份草稿。 +- 处理:只要当前失败页还能恢复到原 `sessionId`,重试就走该 session 的 compile action;只有没有可恢复 session 时,才允许用表单 payload 重新创建草稿。 +- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed .* draft retry reuses current session"`。 +- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + ## 汪汪声浪草稿试玩不要写正式 run - 现象:如果草稿结果页试玩和发布后 runtime 共用同一写成绩路径,未发布或未确认资源的草稿试玩会污染正式单局、排行榜和作品统计。 @@ -1837,6 +1885,132 @@ - 验证:`npx vitest run src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 覆盖访客态纵向滑动不弹登录且触发下一条推荐。 - 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。 +## Windows junction worktree 下 Vitest 定向路径失败先切真实路径 + +- 现象:在 `C:\Users\...\ .codex\worktrees\...` 这类 junction 工作区运行 `npm run test -- src/...` 时,Vitest 可能报 `Failed to load url C:/Users/... (resolved id: F:/DevWorktrees/...)`,同一测试文件明明存在却被判定找不到。 +- 原因:Vite / Vitest 在 Windows 下会把测试入口 realpath 到真实 worktree 路径;如果命令从 junction 路径传入相对文件参数,入口路径和 resolved id 可能跨盘符不一致。 +- 处理:前端定向测试优先从 `Get-Item | Format-List Target` 显示的真实路径运行,例如 `F:\DevWorktrees\codex\worktrees\f584\Genarrative`;不要把这类文件加载失败误判成组件或路由断言失败。 +- 验证:同一命令从真实路径执行应正常收集并运行测试,例如 `npm run test -- src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 +- 关联:`src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx`、`src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`src/routing/appPageRoutes.test.ts`。 + +## 拼消消草稿试玩要和正式 runtime 分流 + +- 现象:拼消消结果页点击“试玩”后如果仍然调用 `/api/runtime/puzzle-clear/runs`,草稿试玩会被正式 run 规则和统计约束卡住,公开作品又可能和草稿恢复串台。 +- 原因:拼消消既有草稿生成 / 结果页 / 发布闭环,也有正式公开 runtime;如果把结果页试玩和公开运行态复用同一个后端 startRun 入口,`work detail` 读取路径和统计口径都会混在一起。 +- 处理:结果页试玩改走前端本地 `runtimeMode=draft` snapshot,只用于草稿试玩和关卡切换,不写正式 run;公开详情和推荐流进入正式 runtime 时才走后端 `/api/runtime/puzzle-clear/*`。客户端读取作品详情时也要区分创作详情 `/api/creation/puzzle-clear/works/{profileId}` 与公开运行态详情 `/api/runtime/puzzle-clear/works/{profileId}`。 +- 验证:点击拼消消结果页的试玩按钮,不应再请求 `/api/runtime/puzzle-clear/runs`;公开详情入口仍应能读取后端运行态详情。 +- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/services/puzzle-clear/puzzleClearClient.ts`、`src/services/puzzle-clear/puzzleClearLocalRuntime.ts`、`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`。 + +## 拼消消 runtime 必须继承拼图模板的原生交互基线 + +- 现象:拼消消卡片在浏览器里会出现原生图片拖拽 / 下载手柄,或窗口拉伸后棋盘和卡片被拉成矩形。 +- 原因:拼消消 runtime 早期只继承了“交换 / 消除”的业务逻辑,没有完整继承拼图模板在基础交互上的防护:`touch-none`、`select-none`、`aspect-square`、`draggable={false}`、`onDragStart(event.preventDefault())`、`-webkit-user-drag: none`。 +- 处理:棋盘容器必须保持正方形约束,卡片按钮和内层 `` 都要显式禁用浏览器原生拖拽,样式层也要补 `user-select: none` 与 `-webkit-user-drag: none`,不能只靠业务指针逻辑。 +- 验证:浏览器中检查棋盘 `getBoundingClientRect().width === height`,卡片图片 `draggable="false"` 且 `-webkit-user-drag` 为 `none`;真实拖拽只应进入交换逻辑,不应触发原生图片拖拽。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/index.css`、`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 + +## 拼消消拖拽浮层要挂到页面级 portal + +- 现象:拼消消拖拽时图片看起来没有贴在鼠标或手指上,尤其是平台壳层本身带有 transform 时更明显。 +- 原因:拖拽 ghost 用了 `position: fixed`,但如果还挂在会被 transform 的局部容器里,浏览器会把 fixed 当成相对该祖先定位;`clientX/clientY` 读到的是视口坐标,两个坐标系一混就会出现肉眼可见的偏移。 +- 处理:拖拽浮层必须通过 portal 挂到 `document.body` 这一层,再继续使用 `clientX/clientY - pointerOffset` 计算 left/top;不要把 ghost 留在平台壳或任何会参与 transform 的容器里。 +- 验证:`npm run test -- src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx` 应断言拖拽浮层父节点是 `document.body`,且 left/top 与按下点偏移一致。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 + +## 拼消消要继承拼图模板的动作语言,不只是规则 + +- 现象:拼消消如果只实现“交换后裁决”,但没有开局翻牌、按下留空位、被替换卡快速飞回、以及局部拼接块整体拖动,玩家会直觉上觉得比原拼图更笨重。 +- 原因:早期实现容易把“规则独立”误读成“动作语言也要重写”,结果只保留了交换逻辑,没有沿用拼图模板里已经验证过的拖拽反馈、空位让位和合并块连续感。 +- 处理:拼消消运行态要继承拼图模板的基础手感:只在开局保留入场翻牌,拖起时源位立即呈空,放下时被替换卡要有明确飞向空位的位移感,连通块要作为整体拖动和整体呈现。 +- 验证:浏览器拖拽时能看到跟手 ghost、源位空槽、落点飞入和整组拼接层;`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx` 应覆盖这些行为。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`src/index.css`。 + +## 拼消消空格位必须允许落位,不能当成不可交互死格 + +- 现象:运行到某一关后,棋盘里出现空格位,用户能看见空洞但拖不进去,也点不动。 +- 原因:空格位被前端交互或后端裁决误当成“无效目标”,只保留了交换逻辑,没有把“源卡落入空位、源位清空”当成合法移动。 +- 处理:空格位必须保留 button 交互态和落点命中逻辑;前端拖拽 / 点击落到空格时直接提交移动,后端和本地 runtime 都要把源卡移动到目标格并清空源格,不再走失败交换。 +- 验证:`npm run test -- src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts`、`cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml player_move_can_drop_card_into_empty_target_cell -- --nocapture`。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/services/puzzle-clear/puzzleClearLocalRuntime.ts`、`server-rs/crates/module-puzzle-clear/src/application.rs`。 + +## 拼消消空位落卡后必须立即补位,不能把空洞留成真空格 + +- 现象:卡牌成功落进空格后,源位仍然留空,玩家会误以为那个格子坏掉了。 +- 原因:移动逻辑只处理了“落到空位”,没有在未消除时同步走一遍重力补位,所以源列会短暂或永久留下空洞。 +- 处理:只要移动后棋盘存在空位,就立即走补位和可解性修复;这样源位会从顶部准备区补卡,不会留下不可交互空洞。 +- 验证:`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts`、`cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml player_move_can_drop_card_into_empty_target_cell -- --nocapture`。 +- 关联:`src/services/puzzle-clear/puzzleClearLocalRuntime.ts`、`server-rs/crates/module-puzzle-clear/src/application.rs`。 + +## 拼消消素材错位先查 sheet 质量门禁 + +- 现象:一张卡牌切片里同时出现两个或多个错位图案,或空白格、相邻编号区域里混入其他图案碎片。 +- 原因:provider 生成的 `1024x1536 / 4x6` 工作表可能违反视觉契约;旧流程只校验布局元数据和切片数量,无法发现图像内容已经主体缺失或污染空白格。边界贴边检测容易把正常铺满主体误判成跨格污染,不能作为高可靠硬门禁。 +- 处理:先强化 atlas prompt,要求每个 `256x256` 单元独立查看时只能包含一个主体或同一主体单一局部;服务端在 sheet 切片前做像素级质量门禁,硬拦截非空格前景占比过低和空白格污染,严重多边非同组边界贴边只记录 warning 供排查,不直接让创作失败。硬门禁失败的 sheet 最多尝试 4 次,仍失败则拒绝持久化脏 atlas。 +- 追加处理:照片式微场景素材必须把每个 `256x256` 单元收束为一张完整的单场景照片裁片;同编号连续格表示同一视觉家族,不是随机独立小图,要求共享同一场景锚点、主色和道具语言。禁止单格内部出现两张照片、两个不同场景、拼接线、内部竖切、内部横切或左右 / 上下两块不同背景;质量门禁只在单格内部强色差直线贯穿大部分高度或宽度,且两侧都像低纹理人工平铺色块时,按“单格内部疑似拼接线”硬失败并重试 sheet,避免把窗框、桌沿、地平线等自然场景强边缘误杀。 +- 追加处理:sheet 生成时如果 VectorEngine 返回 `retryable=true` 的 `502`、`504`、`429` 或请求超时,例如 nginx HTML `502 Bad Gateway`,不要立刻把草稿置为 failed,应消耗同一 sheet 的下一次 attempt;仍失败再回写失败状态。 +- 追加处理:`sheet-03` 原本唯一空白格容易被模型画入主题主体,导致第 6 行第 4 列反复报“空白格有主体”并消耗多次 image2 请求。该格改为 `FILL` 补位格,允许生成主题小图但服务端切片、atlas 合成和运行态全部丢弃;前端拼消消 action 等待窗口同步提高到 40 分钟,避免上游单图慢返回时用户侧 20 分钟超时。 +- 验证:`cargo test -p api-server puzzle_clear --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。 +- 关联:`server-rs/crates/api-server/src/puzzle_clear.rs`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`。 + +## 拼消消锁定组覆盖层必须锚定在棋盘本身 + +- 现象:消除或补牌过程中,局部完成的组图偶尔会看起来从格子里“飘出去”,并且大小会随着窗口和外层面板变化而异常拉伸。 +- 原因:锁定组视觉层用了 `absolute inset-0`,但棋盘容器本身不是 `position: relative`,于是覆盖层实际锚到了更外层的运行态面板,`gridColumn` / `gridRow` 只能在错误坐标系里排版。 +- 处理:棋盘容器必须显式 `relative`,让锁定组覆盖层、拖拽鬼影和格子坐标都在同一正方形棋盘坐标系内排版;不要把这类覆盖层锚到外层 `section` 或整页容器。 +- 验证:浏览器里棋盘 `getBoundingClientRect()` 和锁定组覆盖层应共享同一块正方形区域,窗口缩放后组图不应再出现越界或被拉伸的现象;`PuzzleClearRuntimeShell.test.tsx` 需要断言棋盘 class 包含 `relative`。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。 + +## 拼消消中央场地底图必须挂在棋盘内部 + +- 现象:创作阶段选择了中央场地底图,但运行态消除卡片后只看到浅色格子或空点,看不到底图。 +- 原因:底图被渲染成整页氛围背景,并被页面渐变、棋盘面板和格子 `bg-white/78` 遮住;棋盘内部没有静态底图层,空格仍保留不透明卡片底色。 +- 处理:`boardBackgroundAsset.imageSrc` 必须作为 `puzzle-clear-board` 内部的 `absolute inset-0` 静态底图渲染;空格、消除空位和拖拽源位必须透明或近透明,不能继续使用实体卡片白底。 +- 验证:`PuzzleClearRuntimeShell.test.tsx` 断言 `puzzle-clear-board-background` 在棋盘内,`/board-bg.png` 只出现一次,空格 class 包含 `bg-transparent` 且不包含 `bg-white/78`。 +- 关联:`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 创作入口突然消失先查前后端是否串到不同 worktree + +- 现象:`http://127.0.0.1:3000/` 可访问,但创作 Tab 里新增玩法入口消失;例如 `puzzle-clear` 已在代码默认种子中存在,浏览器仍看不到“拼消消”。 +- 原因:Vite 可能来自当前 worktree,但代理目标的 `api-server` 仍是另一个 worktree 的旧进程,或者 `api-server` 连到旧 SpacetimeDB 模块;此时 `/api/creation-entry/config` 会返回旧入口配置。 +- 处理:先用 `Get-NetTCPConnection -State Listen -LocalPort 3000,8083,3103` 结合 `Get-CimInstance Win32_Process` 确认端口进程路径;停止串线的旧 `api-server`,再用当前 worktree 的 `npm run dev:spacetime -- --spacetime-port --database ` 和 `npm run dev:api-server -- --api-port --spacetime-port --database ` 拉起同一套服务。 +- 验证:`GET /api/creation-entry/config` 应包含目标入口,且监听端口的命令行都指向同一个 worktree;浏览器创作 Tab 对应分类应显示入口卡。 +- 关联:`scripts/dev.mjs`、`.hermes/skills/genarrative-dev-stack-port-routing/SKILL.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## Windows junction 工作区下 dev.mjs 直接执行入口要用 realpath 判断 + +- 现象:在 `C:\Users\...\ .codex\worktrees\...` 这类 junction 路径里运行 `npm run dev:web`,进程会秒退,`3000` 不监听,但同一脚本从真实 worktree 路径能正常启动。 +- 原因:`scripts/dev.mjs` 的入口判断只比对 `process.argv[1]` 和 `import.meta.url` 的字面路径;junction 路径和 realpath 路径不一致时会误判成“不是直接执行”,于是主流程根本不进入。 +- 处理:入口判断改成基于 `realpathSync(...)` 的 `isDirectModuleExecution(...)`,让 junction 路径和真实 worktree 路径指向同一个模块;同时补回归测试覆盖该场景。 +- 验证:`npm run test -- scripts/dev.test.ts scripts/dev-stack-port-utils.test.ts` 通过后,`npm run dev:web -- --web-port 3000 --api-port 8083 --no-interactive` 应能稳定把 `0.0.0.0:3000` 监听起来。 +- 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`。 + +## Vitest 定向测试在 Windows junction 工作区要切真实路径 + +- 现象:在 `C:\Users\...\ .codex\worktrees\...` 这类 junction 路径里跑 `npm run test -- src/...` 时,Vitest 会报 `Failed to load url ... (resolved id: F:/DevWorktrees/...)`,看起来像文件不存在。 +- 原因:Vite / Vitest 会把入口 realpath 到真实 worktree 路径;如果命令从 junction 路径传入相对文件参数,入口路径和 resolved id 可能跨盘符不一致。 +- 处理:前端定向测试优先从真实路径 `F:\DevWorktrees\codex\worktrees\f584\Genarrative` 运行,不要把这类文件加载失败误判成组件或路由断言失败。 +- 验证:同一命令从真实路径执行应正常收集并运行测试。 +- 关联:`src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`src/routing/appPageRoutes.test.ts`。 +- 现象:新增或扩展 `*-generating` 页面后,生成卡只渲染首帧,`已耗时` / `预计等待` 停在进入页那一刻不动。 +- 原因:平台壳层的共享 `miniGameGenerationProgressNowMs` 时钟没有把新生成阶段纳入 tick 条件,或者该阶段的 `buildMiniGameDraftGenerationProgress(..., nowMs)` 没有接入同一时钟。 +- 处理:任何共享生成页都要通过平台壳层统一的时钟判断和 `nowMs` 传递刷新,新增生成阶段时要同时补 `selectionStage` 判定、`useEffect` 依赖和进度调用点。 +- 验证:浏览器里进入对应生成页后,`已耗时` / `预计等待` 应持续变化,不应停在首帧。 + +## 拼消消要用真实可消除判断,不要把“已相邻”当成可解 + +- 现象:拼消消开局或补牌后会直接出现已完成的图案组,或者 `1x2` 被当成半锁定局部留在场上。 +- 原因:早期把可解性写成“场上已经有同组相邻卡”或“只要有一对相邻同组卡就算可解”,这会把已完成盘面误当成合法盘面;同时半锁定规则没有排除 `1x2`。 +- 处理:开局和补牌后的重排必须先排除现成消除,再用真实交换 / 落位模拟判断是否会产生新消除;`1x2` 永远不进入半锁定组,半锁定只允许 `1x3`、`2x2`、`2x3`。 +- 验证:`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx` 与 `cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml -- --nocapture` 通过后,开局盘面不应直接出现 completed group。 +- 关联:`src/services/puzzle-clear/puzzleClearLocalRuntime.ts`、`server-rs/crates/module-puzzle-clear/src/application.rs`。 +## 推荐页作品 key 漏玩法会导致运行内容和标题作者错位 + +- 现象:移动端推荐页进入跳一跳或敲木鱼等作品时,游戏运行内容已经切到当前作品,但下方标题、作者和头像仍显示第一条拼图或其它推荐作品。 +- 原因:平台壳层用 `getPlatformPublicGalleryEntryKey(...)` 写入 `activeRecommendEntryKey`,而 `RpgEntryHomeView` 内部的 `buildPublicGalleryCardKey(...)` 漏掉新玩法 `sourceType` 分支,导致当前 key 查不到条目后回退到推荐列表第一条。 +- 处理:推荐页和平台壳层的公开作品 key 规则必须复用 `buildPlatformPublicGalleryCardKey(...)`,覆盖同一批 `sourceType`,至少包括 `big-fish`、`puzzle`、`jump-hop`、`wooden-fish`、`match3d`、`square-hole`、`visual-novel`、`bark-battle` 和 `edutainment:`;新增玩法公开推荐流时先补这个共享 helper。 +- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile recommend meta matches active"` 应覆盖跳一跳和敲木鱼的当前运行内容、标题和作者一致。 +- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + ## 跳一跳飞行动画不要直接用最新 run 重绘地块窗口 - 现象:跳一跳松手后如果后端很快返回下一帧 run,地块窗口会立刻前移,角色翻腾动画看起来像没播放;若同时刷新图片资产,还可能被误认为地块频闪。 diff --git a/.hermes/shared-memory/project-overview.md b/.hermes/shared-memory/project-overview.md index f1503f39..fdb0b314 100644 --- a/.hermes/shared-memory/project-overview.md +++ b/.hermes/shared-memory/project-overview.md @@ -1,6 +1,6 @@ # Genarrative 项目共享概览 -更新时间:`2026-05-29` +更新时间:`2026-06-03` ## 一句话定位 @@ -10,6 +10,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A - RPG / 自定义世界创作与运行时。 - 拼图玩法创作、草稿、发布、运行态和排行榜。 +- 拼消消玩法创作、素材图集生成、结果页、发布、统一作品详情、正式运行态和基础统计。 - 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。 - 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。 - 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。 diff --git a/CONTEXT.md b/CONTEXT.md index 160d1edd..233cde60 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -18,6 +18,32 @@ _Avoid_: 为每个玩法单独发明素材流水线、把系列素材建模成 ## Language +### Puzzle Clear + +**拼消消**: +基于拼图交换 / 拖拽手感的新玩法模板,玩家移动 1x1 卡牌碎片,把同一复合图案组拼成完整矩形后消除,并由顶部对应纵列补牌继续游玩。 +_Avoid_: 拼图整图过关、三消槽位玩法、前端本地裁决 + +**复合图案组**: +拼消消中可被消除的一幅小图,由 `1x2`、`1x3`、`2x2` 或 `2x3` 的 1x1 卡牌碎片组成;只有组内碎片按正确相对位置拼成完整矩形后才消除。 +_Avoid_: 单张卡牌、整关大图、任意相邻同色块 + +**1x1 卡牌碎片**: +复合图案组被服务端切成的最小可移动单位,带有所属组、形状、组内坐标和图片资产。 +_Avoid_: 前端临时裁图、无所属图案的普通方块 + +**半锁定拼接组**: +非 2 格复合图案组中已经局部完成的拼接状态,可作为整体拖动;玩家用外部单格撞入组内某格时只交换该格,其余部分保留并退回半完成状态。 +_Avoid_: 永久锁死、补牌打散、完整消除 + +**顶部卡牌准备区**: +拼消消棋盘上方按纵列排列的背面卡牌队列;某列产生空位时,准备区对应列的卡牌从顶部下落补齐。 +_Avoid_: 全局随机发牌槽、底部三消槽 + +**防死局发牌**: +拼消消开局和每次补牌后由后端保证至少存在一步可拼接;补牌时至少有一张新掉落卡能与场上剩余某张卡对应。 +_Avoid_: 前端提示代替可解性、完全随机补牌 + ### Wooden Fish **敲木鱼**: diff --git a/docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md b/docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md new file mode 100644 index 00000000..6adfb9a7 --- /dev/null +++ b/docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md @@ -0,0 +1,77 @@ +# 拼消消玩法模板 PRD + +日期:`2026-05-30` + +## 目标 + +新增玩法模板 **拼消消**,工程域与 `playId` 均为 `puzzle-clear`,公开作品码前缀为 `PC-`。拼消消以拼图的交换 / 拖拽手感为原型,但运行态规则独立:玩家移动 1x1 卡牌碎片,把同一复合图案组拼成完整矩形后消除;消除产生空位后,由顶部对应纵列的卡牌准备区下落补位。 + +首版必须完成公开闭环: + +```text +创作入口 -> 轻表单工作台 -> 独立生成页 -> 结果页 -> 试玩 -> 发布 -> 统一作品详情 -> 正式 runtime -> 基础统计 / 作品架 / 广场 +``` + +## 创作工具平台接入声明 + +- 工作台模式:表单 / 图片输入创作工作台。 +- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态。 +- 单图资产槽位: + - `board-background` / `ui-background` / `中央场地底图` / `boardBackgroundPrompt` 优先、空值时回退 `themePrompt`,并支持用户上传图 / 写回 `draft.boardBackgroundAsset`、`draft.boardBackgroundPrompt`、`work.boardBackgroundAsset` 与 `work.boardBackgroundPrompt` / 允许历史图 / 允许 AI 重绘。 + - 中央场地底图的字段名沿用平台表面口径,实际作用是玩家逐步消除清空中央棋盘后慢慢看到的主题目标图;AI 生成尺寸必须与中央棋盘一致,使用 1:1 正方形画面。prompt 必须强绑定主题、画面精致、强表现力并一眼体现主题,带来探索、揭开全貌和追求目标完成的感受;不得继续要求“画面干净”或“适合作为卡牌棋盘底图”。 +- 系列素材槽位: + - `batchId=puzzle-clear-pattern-atlas-v1`。 + - `sheetSpec`:4 张素材工作表,每张 `1024x1536` 竖版,后台按 `4 列 x 6 行` 裁切,每个 1x1 单元为 `256x256`;服务端再把切片合成一张 `10x10 / 2560x2560` 最终 atlas。复合图案组总数为 `35`,形状配比 `1x2=23`、`1x3=5`、`2x2=4`、`2x3=3`,总计 `95` 个 1x1 卡牌切片。 + - `slotSpecs`:每个复合图案组一个 `patternGroup`,服务端预排 `groupId`、`shape`、atlas 坐标和 1x1 切片坐标。 + - 切图规则:生图 prompt 只要求复合图案组能按 4x6 素材工作表均等切成 1x1 方形小份,不允许模型在图上绘制切分线、边框、网格线或裁切参考线;服务端按 sheet 布局直接裁出 1x1 卡牌碎片,校验每个编号占格数与领域图案组面积一致,再合成最终 atlas,写入 `patternGroups[]` 与 `cardAssets[]`。 + - 透明化规则:首版保留完整方形卡面,不强制透明化;若 provider 输出带边框、切分线、网格、裁切参考线或文字,生成任务失败并回写审计。 + - 失败回写:生成页写回 `generationStatus=failed` 与失败阶段;结果页保留重试入口。 + - 局部重生成:v1 允许整批 4 张素材工作表重试,不做单组局部重生。 +- API 命名空间:`/api/creation/puzzle-clear/...` 与 `/api/runtime/puzzle-clear/...`。 +- 业务真相:草稿、发布、runtime snapshot、胜负、补牌、防死局、统计均由后端裁决;前端只做动画和交互表现。 +- 创作工具模式例外:无。 +- 验证命令:`npm run check:encoding`、`npm run typecheck`、`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts`、`npm run test -- src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle_clear --manifest-path server-rs/Cargo.toml -- --nocapture`;涉及 SpacetimeDB schema 后运行 `npm run spacetime:generate`、`npm run check:spacetime-runtime-access`、`npm run check:spacetime-schema`、`npm run check:server-rs-ddd`。 + +## 工作台字段 + +| 字段 | 契约字段 | 默认值 | 校验 | 落库 | +| --- | --- | --- | --- | --- | +| 作品标题 | `workTitle` | 空 | 必填,1-30 字 | session draft / work profile | +| 简介 | `workDescription` | 空 | 0-120 字 | session draft / work profile | +| 主题词 | `themePrompt` | 空 | 必填,1-80 字 | 生成 prompt 与草稿 | +| 场地底图主题词 | `boardBackgroundPrompt` | 空 | 0-80 字;为空时底图生成回退 `themePrompt` | session draft / work profile / 主题目标图生成 prompt | +| 中央场地底图 | `boardBackgroundAsset` | 空 | 上传或 AI 生成至少一种 | 单图资产槽位 | +| AI 生成底图 | `generateBoardBackground` | `true` | boolean | 生成编排参数 | + +规则参数不开放创作者编辑:棋盘尺寸、倒计时、消除次数、形状解锁、防死局发牌和半锁定规则固定。 + +## 运行规则 + +| 关卡 | 棋盘 | 目标消除 | 倒计时 | 解锁形状 | +| --- | --- | --- | --- | --- | +| 1 | 6x6 | 35 | 10 分钟 | 1x2、1x3、2x2、2x3 | + +- 开局每个小格子从背面翻向正面。 +- 可消除图由横向或纵向复合图案组组成,最小消除单位为两张图拼接。 +- 完成一个复合图案组后,该组所有 1x1 卡牌碎片消除。 +- 消除后空位按列由顶部卡牌准备区下落补齐。 +- 每次补牌至少保证掉落卡中有一张可以与场上剩余某张卡拼接,防止死局。 +- 非 2 格消除时,若场上已有局部完成的半锁定拼接组,补牌不得破坏它。 +- 半锁定拼接组可整体拖动;玩家用外部单格撞入组内某格时,只交换该格,组其余部分保留,组状态退回半完成。 +- 超时只判当前关失败,可重试当前关;完成 35 次目标并清空当前棋盘后整局完成。 + +## 结果页 + +结果页展示:素材 atlas、中央场地底图、发布状态、试玩入口和失败重试。结果页不写功能说明类文案,不开放规则编辑器,不新增排行榜配置。 + +## 统计 + +首版只记录正式 `published` run: + +- 开局。 +- 全局完成。 +- 当前关失败。 +- 耗时。 +- 消除统计。 + +草稿试玩不写正式统计,不进入排行榜;v1 不做排行榜。 diff --git a/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md b/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md index 6bf23f8e..218b02f0 100644 --- a/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md +++ b/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md @@ -205,10 +205,11 @@ WF-* 1. 若 payload 已包含上传/录音音频资产,`compile-draft` 跳过音效生成,直接持久化该资产; 2. 若 payload 已上传或录制音频,则直接写回 `hitSoundAsset`; -3. 若两者都没有,后端写回默认木鱼音 `/wooden-fish/default-hit-sound.mp3`; -4. 音效资产必须包含可播放地址、对象键、asset object id、来源和可选时长; -5. 通用创作音频接口当前对 `wooden_fish` 的 `hit_sound` 目标返回 `410 Gone`,不得在创作流程中按提示词生成音效; -6. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径;缺少真实 `hitSoundAsset` 时应使用默认木鱼音兜底展示与播放。 +3. 麦克风录制音频在保存前由前端自动裁掉开头连续静音段;上传音频不做裁剪,裁剪失败时保留原始录音继续保存; +4. 若两者都没有,后端写回默认木鱼音 `/wooden-fish/default-hit-sound.mp3`; +5. 音效资产必须包含可播放地址、对象键、asset object id、来源和可选时长; +6. 通用创作音频接口当前对 `wooden_fish` 的 `hit_sound` 目标返回 `410 Gone`,不得在创作流程中按提示词生成音效; +7. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径;缺少真实 `hitSoundAsset` 时应使用默认木鱼音兜底展示与播放。 ### 6.3 封面 @@ -371,7 +372,7 @@ finish 音频播放: -1. 前端使用小复音池; +1. 前端使用 10 路小复音池; 2. 设置最小播放间隔,避免极端连点导致浏览器抖动; 3. 点击计数不能因为音频节流而丢失; 4. 签名 URL 未就绪时先静音表现,不请求裸 generated 私有路径。 diff --git a/docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md b/docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md new file mode 100644 index 00000000..b66efa99 --- /dev/null +++ b/docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md @@ -0,0 +1,123 @@ +# 拼消消玩法模板技术方案 + +日期:`2026-05-30` + +## 总体边界 + +拼消消使用独立工程域 `puzzle-clear`,不复用拼图运行态规则本体。实现按 DDD 分层: + +- `module-puzzle-clear`:纯领域规则,覆盖图案组规划、棋盘、交换、半锁定、消除、补牌、防死局、关卡状态。 +- `shared-contracts` / `packages/shared`:工作台输入、生成素材、结果页、作品摘要、runtime snapshot 与 action DTO。 +- `spacetime-module`:session、work profile、runtime run、事件 / 统计、公开 source view。 +- `spacetime-client`:typed facade 与 row mapper。 +- `api-server`:Axum 路由、鉴权、入口熔断、生成编排、资产持久化、BFF。 +- `platform-image` / OSS / asset object:图片生成、切图、上传、换签和失败审计。 +- 前端:轻表单、生成页、结果页与 runtime 动画,不承接正式业务真相。 + +## 资产生成方案 + +素材目标从“单张超大 atlas 生图”收敛为 4 张素材工作表,再由服务端合成最终 atlas: + +- image2 调用:4 次,每次生成 1 张 `1024x1536` 竖版素材工作表。 +- sheet 裁切:每张按 `4 列 x 6 行` 裁切,每个 1x1 单元为 `256x256`。 +- 最终 atlas:服务端把 95 个切片按领域坐标合成 `10x10 / 2560x2560` PNG,空单元保留浅色背景。 +- 运行态素材:最终写回 `35` 个复合图案组和 `95` 个 1x1 卡牌切片;`sheet-03` 的第 6 行第 4 列为 `FILL` 补位格,只为填满 4x6 工作表,生成后会被服务端丢弃,不进入最终 atlas 或运行态卡牌。 + +服务端固定布局如下: + +| 形状 | 数量 | 单组单元数 | 解锁 | +| --- | ---: | ---: | --- | +| 1x2 | 23 | 2 | 第 1 关 | +| 1x3 | 5 | 3 | 第 1 关 | +| 2x2 | 4 | 4 | 第 1 关 | +| 2x3 | 3 | 6 | 第 1 关 | + +流程: + +```text +主题词 / 场地底图主题词 / 用户底图 -> 4 张 sheet 坐标规划 -> gpt-image-2 生成素材工作表 -> 按 4x6 裁切 1x1 -> 合成最终 atlas -> atlas 与卡牌切片持久化 -> OSS / asset_object / bind -> session draft 回写 +``` + +中央场地底图的 prompt 来源固定为:若用户填写 `boardBackgroundPrompt`,AI 生成底图只读取该字段;若该字段为空,才回退读取 `themePrompt`。用户直接上传底图资产时不再用主题词重写该资产,只执行平台资产持久化与换签。中央场地底图在运行态不是普通棋盘衬底,而是玩家逐渐消除卡牌后露出的主题目标图;生成请求使用与中央棋盘一致的 1:1 正方形尺寸,prompt 必须强调探索、揭开全貌、追求完成目标、精致主题主视觉和强主题表现,不写“画面干净”或“适合作为卡牌棋盘底图”。 + +### 素材工作表风险与切片验证 + +风险:4x6 工作表 prompt 仍需要告诉 provider 编号布局;如果模型把布局理解成 UI 海报、说明图或卡牌模板,可能画出文字、编号、边框、切分线、贴纸外框或重复主体。若 provider 无法严格按布局输出,切片后可能出现跨格、主体贴边、重复图案、文字或图案错位。 + +验证策略: + +- 生图 prompt 明确要求照片式构图 / 绘本式渲染的主题微场景拼图卡,每个 256x256 单元本身就是一张完整的单场景照片裁片,单元内部只能有一个连续画面,禁止出现两张照片、两个不同场景、拼接线、分割线、内部竖切、内部横切、左右 / 上下两块不同背景,场景变化只能发生在 256 单元边界上。 +- 同编号连续格表示同一视觉家族,不是随机独立小图;同组格子要共享同一场景锚点、主色和道具语言,像同一套连拍或同一场景的不同局部,彼此能看出是同一个故事或场景家族。 +- 同一张 sheet 内不同编号必须发散成不同视觉概念;以水果为例,应扩展为果园、集市摊位、野餐布、果汁杯、厨房案板、甜品盘、篮筐、玻璃罐、窗边餐桌、花园背景等微场景,禁止同品种主体换角度、换大小或换姿势后重复出现。 +- 每个 256x256 小卡切片独立查看时也要有可辨识的背景纹理、桌面、草地、天空、建筑、布料、器皿、叶片、阴影或装饰元素,避免“孤立主体 + 纯色背景”导致运行态难区分。 +- 生图 prompt 明确禁止文字、水印、UI、边框标签、切分线、网格线、裁切参考线、纯色背景、白底商品图、孤立主体、同品种重复和同一物体多角度。 +- 复合图案组本身不画任何可见分割辅助线,但 prompt 必须说明每个 `1x2`、`1x3`、`2x2`、`2x3` 图案都能被服务端按等大的 1x1 方形单元切分;纵向 `1x2` 按横向切线分成两个 1x1,横向 `1x2` 按纵向切线分成两个 1x1,其他形状同理。图案组可以在语义上成组,但不能把一张大图的照片边界或拼贴边界落在单个 1x1 单元内部。 +- 服务端保留 `PuzzleClearPatternGroup` 坐标清单,切片前校验每个 sheet 正式编号出现次数等于领域图案组 `width * height`,并要求同编号区域是完整连续矩形;`FILL` 补位格不参与校验、切片、atlas 合成和运行态。 +- 每张 sheet 生成后、正式切片前执行像素级质量门禁:非空格必须达到最低前景占比,空白格前景占比不得超阈值,单格内部明显人工拼贴式分割需要硬失败;内部强边缘检测必须同时满足“贯穿大部分高度或宽度”和“两侧近似低纹理平铺色块”,避免把照片式微场景里的窗框、桌沿、地平线等自然结构误杀。非同组边界前景贴边仅记录为质量提示,不作为硬失败,避免把模型正常铺满主体的图集误杀。 +- 每张 sheet 生成最多尝试 4 次;除质量门禁失败外,VectorEngine 返回 `retryable=true` 的 `502`、`504`、`429` 或请求超时也应消耗下一次 sheet attempt,避免上游 nginx 偶发 502 或单次拼贴式坏图直接把草稿置为 failed。 +- 前端拼消消创作 action 的请求等待窗口为 40 分钟,用于覆盖 VectorEngine 单张图偶发 10 分钟以上的慢返回;这只是本地验收稳定性兜底,后续若继续优化体验,应把素材生成迁到后台任务 / 轮询进度链路。 +- sheet 多次生成仍未通过硬质量门禁时,生成任务进入 `failed` 并写入错误原因;不得把明显空白格污染或主体缺失的工作表切成正式卡牌资产。 +- 首版若当前 provider 无法稳定产出可切 atlas,生成任务进入 `failed`,错误写入审计;不得退回前端假素材或绕过平台资产底座。 +- 草稿编译和作品发布都必须拒绝缺失 atlas、缺失卡牌切片、空 `assetObjectId` / `imageObjectKey` 或 `placeholder` 占位资产;`spacetime-client` 不再为编译请求合成默认 atlas / card assets。 +- 技术回退需要用户确认后才能改成更多 sheet、降低切片规格或改为逐图生成;当前需求固定为 4 张 `1024x1536` sheet 与最终 `2560x2560` atlas。 + +## 领域规则 + +`module-puzzle-clear` 已固定以下规则: + +- 关卡配置:单关 `6x6/35`,600 秒。 +- 图案组配比:`1x2=23`、`1x3=5`、`2x2=4`、`2x3=3`。 +- 开局随机铺满并保证至少一步可解。 +- 补牌按列重力下落;补牌后仍保证至少一步可解。 +- 完整图案组消除并清空对应格。 +- 半锁定拼接组只由玩家主动交换 / 撞入打散,补牌不破坏。 +- 超时失败只作用于当前单关,可重试;完成 35 次消除目标并清空棋盘后整局完成。 + +## API 命名空间 + +- `POST /api/creation/puzzle-clear/sessions` +- `GET /api/creation/puzzle-clear/sessions/{sessionId}` +- `POST /api/creation/puzzle-clear/sessions/{sessionId}/actions` +- `GET /api/creation/puzzle-clear/works` +- `GET /api/creation/puzzle-clear/works/{profileId}` +- `POST /api/creation/puzzle-clear/works/{profileId}/publish` +- `GET /api/runtime/puzzle-clear/works/{profileId}` +- `POST /api/runtime/puzzle-clear/runs` +- `POST /api/runtime/puzzle-clear/runs/{runId}/swap` +- `POST /api/runtime/puzzle-clear/runs/{runId}/retry-level` +- `POST /api/runtime/puzzle-clear/runs/{runId}/next-level` +- `POST /api/runtime/puzzle-clear/runs/{runId}/time-up` + +api-server 路由熔断使用 SpacetimeDB 创作入口配置 `puzzle-clear`,不得新增前端硬编码事实源。 + +## Runtime 事件与统计载荷 + +正式 `published` run 记录开局、全局完成、当前关失败、耗时和消除统计。runtime action 返回的终态事件包括: + +- `run-finished`:第 1 关完成并结束整局,结果 JSON 至少包含 `status`、`level`、`clears`、`clearDelta`、`elapsedMs`。 +- `level-failed`:当前关超时失败,结果 JSON 至少包含 `status`、`level`、`clears`、`clearDelta`、`elapsedMs`。 + +草稿试玩只消费同一份 snapshot/action 结果做表现,不写正式统计。 + +## 前端阶段 + +新增阶段: + +- `puzzle-clear-workspace` -> `/creation/puzzle-clear` +- `puzzle-clear-generating` -> `/creation/puzzle-clear/generating` +- `puzzle-clear-result` -> `/creation/puzzle-clear/result` +- `puzzle-clear-runtime` -> `/runtime/puzzle-clear` + +runtime 移动端优先,首屏结构为顶部倒计时 / 单关铭牌、顶部列准备区、棋盘、失败 / 完成弹层。棋盘主网格、半锁定组覆盖层和消除 / 掉落覆盖层统一使用 1.5px 格间距。动画包括开场翻转、局部正确拼合高光、完整消除放大淡出和列补牌延迟下落,不再有下一关切换。消除和补牌动画只能作为当前后端 snapshot 的表现层覆盖;已有场上卡片因重力下沉后的最终格不得被旧消除坐标或掉落覆盖层隐藏,避免出现“下方空位但上方卡片未下落”的视觉假象;新补入卡牌应等完整消除淡出进入尾段后再播放下落反馈。 + +## 验证计划 + +- `cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml` +- `cargo test -p api-server puzzle_clear --manifest-path server-rs/Cargo.toml -- --nocapture` +- `cargo test -p spacetime-client --manifest-path server-rs/Cargo.toml puzzle_clear_compile_requires_real_atlas_assets_from_api_server` +- `npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts` +- `npm run test -- src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx` +- `npm run test -- src/routing/appPageRoutes.test.ts src/services/publicWorkCode.test.ts` +- `npm run check:encoding` +- `npm run typecheck` +- 接入 SpacetimeDB schema 后:`npm run spacetime:generate`、`npm run check:spacetime-runtime-access`、`npm run check:spacetime-schema`、`npm run check:server-rs-ddd` diff --git a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md index a5fc8c2b..883e6663 100644 --- a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md +++ b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md @@ -334,7 +334,7 @@ npm run check:server-rs-ddd - Rust 结构体:`CreationEntryConfig` - 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` - 字段:`config_id`、`start_title`、`start_description`、`start_idle_badge`、`start_busy_badge`、`modal_title`、`modal_description`、`updated_at`、`event_title`、`event_description`、`event_cover_image_src`、`event_prize_pool_mud_points`、`event_starts_at_text`、`event_ends_at_text`、`event_banners_json`。 -- 迁移兼容:旧迁移包缺少活动横幅字段时,由 `migration.rs` 写入 `None` / `58000` 默认值;旧库缺少 `event_banners_json` 时写入 `None`,运行态读取层再按 `module-runtime` 默认公告数组归一,不覆盖后台已保存配置。HTTP 响应同时返回 `eventBanners` 数组和旧 `eventBanner` 单条兼容字段,前端优先消费数组;后台新配置主格式为 HTML 公告字符串数组或 `{title, htmlCode}` 对象数组,旧结构化 banner 字段仅保留兼容。 +- 迁移兼容:旧迁移包缺少活动横幅字段时,由 `migration.rs` 写入 `None` / `58000` 默认值;旧库缺少 `event_banners_json` 时写入 `None`,运行态读取层再按 `module-runtime` 默认公告数组归一,不覆盖后台已保存配置,也不把旧结构化 `eventBanner` 升格为前端优先数组。HTTP 响应同时返回 `eventBanners` 数组和旧 `eventBanner` 单条兼容字段,前端优先消费数组;后台新配置主格式为 HTML 公告字符串数组或 `{title, htmlCode}` 对象数组,旧结构化 banner 字段仅保留兼容。默认公告背景和旧结构化默认 `coverImageSrc` 必须引用 `public/` 下真实存在的静态资源,当前为 `/creation-type-references/puzzle.webp`。 ### `creation_entry_type_config` @@ -632,6 +632,45 @@ npm run check:server-rs-ddd - Rust 结构体:`PuzzleWorkProfileRow` - 源码:`server-rs/crates/spacetime-module/src/puzzle.rs` +### `puzzle_clear_agent_session` + +- Rust 结构体:`PuzzleClearAgentSessionRow` +- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs` +- 说明:拼消消创作会话表,保存轻表单草稿、生成状态、已发布 profile 关联和更新时间;只由拼消消 procedure 读写。 + +### `puzzle_clear_work_profile` + +- Rust 结构体:`PuzzleClearWorkProfileRow` +- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs` +- 说明:拼消消作品 profile 表,保存中央底图资产、4 张素材工作表切片后合成的最终 atlas、35 个复合图案组、95 个 1x1 卡牌切片、卡背占位图、发布状态、可见性和基础 play count;公开列表 / 详情只通过 read model 消费,不让前端直接订阅源表。 +- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。 + +### `puzzle_clear_runtime_run` + +- Rust 结构体:`PuzzleClearRuntimeRunRow` +- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs` +- 说明:拼消消正式 runtime run 表,保存当前关卡、已消除次数、棋盘 snapshot、开始 / 完成时间和 run 状态;正式胜负、重试、完成、超时和交换结果以后端 procedure 裁决为准。 + +### `puzzle_clear_event` + +- Rust 结构体:`PuzzleClearEventRow` +- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs` +- 说明:拼消消基础 runtime 事件表,记录 published run 的开局、关卡完成、全局完成、失败、超时和消除统计来源;首版不做排行榜。 + +### SpacetimeDB view:`puzzle_clear_gallery_view` + +- Rust view:`puzzle_clear_gallery_view` +- 返回类型:`Vec` +- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear.rs` +- 说明:拼消消公开详情 source 投影,只暴露 `publication_status = published` 且 `visible = true` 的作品,包含 atlas、底图、图案组和卡牌切片等详情级字段;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。 + +### SpacetimeDB view:`puzzle_clear_gallery_card_view` + +- Rust view:`puzzle_clear_gallery_card_view` +- 返回类型:`Vec` +- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear.rs` +- 说明:拼消消公开列表 source 投影,只暴露平台卡片需要的公开字段;统一公开列表主路径通过 `public_work_gallery_entry` 消费该 view,`/api/runtime/puzzle-clear/gallery` 保留玩法专属 HTTP shape。 + ### SpacetimeDB view:`puzzle_gallery_view` - Rust view:`puzzle_gallery_view` @@ -661,6 +700,7 @@ npm run check:server-rs-ddd - `SELECT * FROM public_work_detail_entry` - `SELECT * FROM bark_battle_gallery_view` - `SELECT * FROM puzzle_gallery_card_view` +- `SELECT * FROM puzzle_clear_gallery_card_view` - `SELECT * FROM jump_hop_gallery_card_view` - `SELECT * FROM wooden_fish_gallery_card_view` - `SELECT * FROM custom_world_gallery_entry` @@ -677,6 +717,7 @@ npm run check:server-rs-ddd - `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'visual-novel'` - `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'big-fish'` - `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'bark-battle'` +- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle-clear'` - `SELECT * FROM creation_entry_config` - `SELECT * FROM creation_entry_type_config` - `SELECT * FROM asset_object` diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index e2e05bd8..16e7939c 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -1,4 +1,4 @@ -# 本地开发验证与生产运维 +# 本地开发验证与生产运维 更新时间:`2026-06-05` @@ -51,6 +51,8 @@ Linux 本机多用户并发开发时,`npm run dev` 和 `npm run dev:*` 单模 开发态 `npm run dev` 与 `npm run dev:api-server` 会默认注入 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,因此密码登录在本地开发环境可直接注册未知手机号账号;生产环境仍按 `api-server` 配置默认关闭该开关。 +本地只做账号/UI smoke 且需要短信登录时,`SMS_AUTH_PROVIDER` 应显式设为 `mock`,并把 `SMS_AUTH_MOCK_VERIFY_CODE` 设为固定值(当前常用 `123456`),再重启 `npm run dev` 或 `npm run dev:api-server`。如果 `.env.local` 还保留 `SMS_AUTH_PROVIDER=aliyun`,`POST /api/auth/phone/login` 用 mock 验证码会稳定报“验证码错误”,不是前端表单问题。真实短信联调再切回 `aliyun` 并重启。 + 微信小程序虚拟支付使用 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_OFFER_ID`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY` 和 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV` 配置。小程序充值统一走 `wechat_mp_virtual` / `wx.requestVirtualPayment`:泥点属于代币(`coin`),`buyQuantity` 按当前充值商品快照里的 `points_amount` 传;会员和后台新增道具类商品走 `short_series_goods`,`productId` 对应微信后台道具 ID。旧登录快照若缺 `session_key`,需要用户在小程序内重新登录后再支付;客户端成功回调不是最终到账,仍以后端通知或查询确认订单为准。详细口径见 `docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。 如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,或 `api-server` 日志出现 `no such table: puzzle_gallery_card_view` / `no such table: wooden_fish_gallery_card_view` 这类公开 view 缺失,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。 @@ -71,6 +73,8 @@ spacetime sql "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --serv VectorEngine 图片生成 / 编辑在 `request_send` 阶段出现 `timeout` 或 `connect` 错误时,`platform-image` 会对同一请求最多发送 3 次;multipart 图片编辑每次重试都会重新构造 form,避免复用已消费的 body。日志中 `VectorEngine 图片请求发送失败,准备重试` 表示本次失败已进入下一次尝试;最终仍失败时才会写入 `external_api_call_failure` 并返回 504。排查生产失败时应同时统计 retry 前的尝试日志和最终 audit,避免把一次用户请求内的多次发送误判成多个用户请求。 +拼图入口直创的 `compile_puzzle_draft` 是长耗时链路:后端会先快速编译草稿并返回 `image_refining` / `generating` 快照,然后在 api-server 后台任务中完成首图、UI 资产、OSS 持久化、作品投影、计费退款和失败态回写。生产排查小程序 `Failed to fetch` 时,若 Nginx access log 里 action POST 是 `499`、`upstream_status=-`,说明客户端或 WebView 先断开;此时不应再把长 POST 是否返回作为生成成败依据,而应继续按实际 `session_id` 查后台任务日志、VectorEngine provider 日志、`external_api_call_failure` 和后续 GET 轮询结果。同一用户可能先轮询旧的 `puzzle-session-*`,随后 POST 新建实际生成 session;必须用 action POST 的 `request_id` 和 `/api/runtime/puzzle/agent/sessions//actions` 路径对齐真实失败请求,避免被前端显示的“来源草稿”误导。 + 查看本地 Rust / SpacetimeDB 日志: ```bash @@ -353,7 +357,7 @@ cargo test -p platform-auth --manifest-path server-rs/Cargo.toml aliyun_send_sms - `profile_task_reward_claim` - `profile_wallet_ledger` -个人任务首版 scope 仅支持 `user`。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`。 +个人任务首版 scope 仅支持 `user`。每日登录任务按北京时间自然日 0 点重置;用户已登录并停留在“我的”页跨日时,前端需要先非阻断调用 refresh session 以写入新业务日 `daily_login`,再请求 `/api/profile/tasks` 刷新任务中心。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`。 外部 API 失败审计复用 `tracking_event`,不新增表。失败事件优先写入本机 tracking outbox,再由后台 worker 批量落库;如果 outbox 因权限、磁盘或保护阈值不可写,会回退同步直写 SpacetimeDB。`metadata_json` 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、errorSource、latencyMs、promptChars、referenceImageCount、imageModel、rawExcerpt、userId、profileId 和 requestId;其中 `userId` 是触发生成的用户,`profileId` 是调用方传入的草稿 / 作品 / 场景作用域,`requestId` 用于回查同一次 HTTP 请求日志,入口拿不到上下文时允许为空。常用查询: diff --git a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md index 2b1ee83d..9f950b43 100644 --- a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md +++ b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md @@ -1,12 +1,14 @@ # 平台入口与玩法链路 -更新时间:`2026-06-03` +更新时间:`2026-06-04` ## 平台创作入口 创作入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;后台通过 `/admin/api/creation-entry/config` 管理。前端只在展示层派生可见卡片和入口状态,`api-server` 路由熔断也使用同一份配置。不要恢复前端硬编码入口配置文件。 -当前点击底部加号进入的创作入口页承载后台公告位、创作入口页签和两列模板卡;页签中只有真实后端作品架摘要存在时才展示“最近创作”,其余为玩法模板分类。点击模板卡后直接进入对应玩法已有的入口创作表单 stage,不再经过空白占位页,也不把旧表单嵌进创作入口页。移动端创作入口页顶栏在 `陶泥儿` 品牌同一行显示真实账户泥点数,数据来自 `profileDashboard.walletBalance`,不得再把公告内容或活动奖池当作账号余额展示。创作入口页公告位数据优先读取 `GET /api/creation-entry/config` 的 `eventBanners` 数组,多条配置时前端自动轮播,旧 `eventBanner` 仅作为单条兼容兜底。后台公告配置面向表单:每条公告包含标题和 HTML 内容,后台保存时序列化为后端 `eventBannersJson` 传输字段,由前端空权限沙箱 iframe 展示;旧结构化 banner 字段仅保留回显兼容,不再作为后台公告配置主格式;不得执行 JSX 或把后台代码直接注入 DOM。玩法列表不再套外部边框卡片,移动端需要压缩横向边距和两列间距;玩法卡统一按“上图、左上状态标签(仅非开放态显示)、封面右下 `10-20泥点数`、下方白底标题/描述”结构展示,卡片高度保持紧凑但标题、描述和预估消耗点数都必须可见。创作入口页根容器不再使用 `platform-page-stage` 这类全局内容卡片壳,但继续保留 `platform-remap-surface` 作为主题和输入框样式命中钩子。创作入口页字号需要对齐平台普通 UI 档位:顶栏泥点组件、公告正文、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,不使用 `text-lg`、`text-xl` 或更大的展示级字号。草稿 Tab 继续承接作品架;底部加号入口页的“最近创作”只用 7 天内的真实后端作品架摘要判断是否展示,并从摘要里推导最近使用过的模板 ID,页面必须展示“仅显示最近7天内使用过的模板”提示,列表内容必须复用其它页签里的模板卡样式、文案和点击行为,不展示具体作品名称、摘要或生成状态,也不新增独立最近创作卡组件。RPG、RPG 之外的各玩法入口分别落到既有的 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`,这些入口继续承接各玩法自己的表单、草稿恢复和后续编排,不作为创作入口页内容。 +当前点击底部加号进入的创作入口页承载后台公告位、创作入口页签和两列模板卡;页签中只有真实后端作品架摘要存在时才展示“最近创作”,其余为玩法模板分类。点击模板卡后直接进入对应玩法已有的入口创作表单 stage,不再经过空白占位页,也不把旧表单嵌进创作入口页。移动端创作入口页顶栏在 `陶泥儿` 品牌同一行显示真实账户泥点数,数据来自 `profileDashboard.walletBalance`,不得再把公告内容或活动奖池当作账号余额展示。创作入口页公告位数据优先读取 `GET /api/creation-entry/config` 的 `eventBanners` 数组,多条配置时前端自动轮播;旧 `eventBanner` 只保留字段回显与旧客户端兼容,不再作为前端公告数组的兜底来源。后台公告配置面向表单:每条公告包含标题和 HTML 内容,后台保存时序列化为后端 `eventBannersJson` 传输字段,由前端空权限沙箱 iframe 展示;旧结构化 banner 字段仅保留回显兼容,不再作为后台公告配置主格式;不得执行 JSX 或把后台代码直接注入 DOM。玩法列表不再套外部边框卡片,移动端需要压缩横向边距和两列间距;玩法卡统一按“上图、左上状态标签(仅非开放态显示)、封面右下 `10-20泥点数`、下方白底标题/描述”结构展示,卡片高度保持紧凑但标题、描述和预估消耗点数都必须可见。创作入口页根容器不再使用 `platform-page-stage` 这类全局内容卡片壳,但继续保留 `platform-remap-surface` 作为主题和输入框样式命中钩子。创作入口页字号需要对齐平台普通 UI 档位:顶栏泥点组件、公告正文、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,不使用 `text-lg`、`text-xl` 或更大的展示级字号。草稿 Tab 继续承接作品架;底部加号入口页的“最近创作”只用 7 天内的真实后端作品架摘要判断是否展示,并从摘要里推导最近使用过的模板 ID,页面必须展示“仅显示最近7天内使用过的模板”提示,列表内容必须复用其它页签里的模板卡样式、文案和点击行为,不展示具体作品名称、摘要或生成状态,也不新增独立最近创作卡组件。RPG、RPG 之外的各玩法入口分别落到既有的 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`,这些入口继续承接各玩法自己的表单、草稿恢复和后续编排,不作为创作入口页内容。 + +旧库或旧迁移包没有 `event_banners_json` 时,后端读取层必须把 `eventBanners` 归一到 `module-runtime` 默认公告数组,不能把旧结构化 `eventBanner` 当成前端优先数组下发。默认公告引用的背景图必须指向 `public/` 下真实存在的站内静态资源,当前默认使用 `/creation-type-references/puzzle.webp`,避免创作入口顶部 banner 出现失效图片。 创作页和草稿页顶栏右上角的泥点余额胶囊是补足泥点入口:如果当前运行环境开启充值入口,点击后直接打开账户充值弹窗;否则直接打开运营兑换码弹窗。该入口不再跳到账户面板或泥点账单,头像 / 设置等账号入口继续保留各自语义。 @@ -44,25 +46,25 @@ 通用系列素材图集能力的实现真相源在 `platform-image::generated_asset_sheets`:`n` 是必选参数,模块负责组装 `n*n` sheet prompt、按 `n*n` 切片、默认绿幕 / 近白底透明化、导出 PNG 和 OSS 持久化请求;高风险撞色玩法可显式使用专用 key 色、关闭近白扣除并限制为边缘连通背景扣除。`api-server::generated_asset_sheets` 只保留 `AppError` / `AppState` 适配,不再承载图像处理和 OSS 请求构造细节。物品名称 prompt 和特殊设定 prompt 是可选输入;调用方可传入类似“每个物品生成五个不同视图”的视角约束,通用模块会把 sheet prompt、物品行 prompt、特殊设定 prompt 编码写入 OSS 元数据。玩法仍负责计费、物品规划、slot 映射、失败回写和把通用切片结果映射回自己的草稿 / profile / runtime 字段。 -当前所有玩法生成页 UI 统一收敛为圆环主视觉:`media/create_bg_video.mp4` 作为生成页固定全屏背景层循环静音播放,主进度圆环居中覆盖在背景之上,围绕陶泥儿视觉展示;页面只保留当前步骤名称和当前步骤进度,不再渲染步骤列表块。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。圆环内部保持 `400x400` SVG 坐标系,外层显示宽度以 `400px` 为上限,窄屏按视口宽度收缩,预计等待 / 已耗时信息卡在窄屏下落到圆环下方,避免右侧裁切。共用生成页 `CustomWorldGenerationView` 和汪汪声浪生成页都必须遵循这一口径。 +当前所有玩法生成页 UI 统一收敛为圆环主视觉:`media/create_bg_video.mp4` 作为生成页固定全屏背景层循环静音播放,主进度圆环居中覆盖在背景之上,围绕陶泥儿视觉展示;页面只保留当前步骤名称和当前步骤进度,不再渲染步骤列表块,也不再展示“当前拼图信息”“当前敲木鱼信息”“当前世界信息”等玩法设定信息模块。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。圆环内部保持 `400x400` SVG 坐标系,外层显示宽度以 `400px` 为上限,窄屏按视口宽度收缩,预计等待 / 已耗时信息卡在窄屏下落到圆环下方,和当前步骤卡保持更大的垂直间距;预计等待左边缘、已耗时右边缘必须分别与当前步骤卡左右边缘对齐,避免右侧裁切或横向漂移。生成页顶部返回栏和状态标识不参与内容滚动,滚动只发生在进度内容区。共用生成页 `CustomWorldGenerationView` 和汪汪声浪生成页都必须遵循这一口径。 ## 草稿与作品架 1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。 2. 草稿页顶部 `全部 / 草稿 / 已发布` 筛选与发现页 `推荐 / 今日 / 分类 / 排行` 频道标签复用同一选中 / 未选中视觉,即 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`,不再使用旧 `platform-tab` 胶囊样式。 -3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作在作品卡上也要直接开放独立删除入口,左滑或长按仅作为辅助操作层。 +3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作在作品卡上也要直接开放统一 `actions.delete` 入口,左滑、长按和键盘左箭头仅作为打开同一操作层的辅助交互;所有玩法草稿和已发布列表项都必须通过该统一接口接入删除确认、删除中状态和列表刷新,不允许只给拼图保留专属滑动删除分支。 4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。 5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。 6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 优先使用后端 session 的 `updatedAt`,没有 session 时再使用作品摘要 `updatedAt`,不得因重新进入页面从 0 秒重新计时。 -7. 生成失败必须按 session 独立记录,不能用一个失败打断或覆盖同玩法的其它生成任务。失败 notice 需要保存错误消息并覆盖作品架本地状态:即使后端摘要暂时仍是 `generationStatus=generating` 或只写出半成品投影,草稿卡也不得继续显示“生成中”,点击后必须进入失败 / 重试生成页,不能重新创建一轮生成;拼图这类失败半成品若没有有效 `workTitle`,作品架标题回退为“拼图草稿”,不暴露“第1关”空壳。 +7. 生成失败必须按 session 独立记录,不能用一个失败打断或覆盖同玩法的其它生成任务。失败 notice 需要保存错误消息并覆盖作品架本地状态:即使后端摘要暂时仍是 `generationStatus=generating` 或只写出半成品投影,草稿卡也不得继续显示“生成中”,点击后必须进入失败 / 重试生成页,不能重新创建一轮生成。失败页点击重新生成时必须优先复用当前可恢复 `sessionId` 执行编译 action;只有没有可恢复 session 时才允许回退到新建草稿。拼图这类失败半成品若没有有效 `workTitle`,作品架标题回退为“拼图草稿”,不暴露“第1关”空壳。 8. 从草稿 Tab 作品架打开草稿工作区、生成页或结果页时,返回按钮必须回到草稿 Tab 的同一作品架语境;从创作 Tab 新建或直接进入创作链路时才回到创作 Tab。平台壳层需要显式记录本次创作流的返回来源,不能让结果页返回动作固定跳到创作入口。 9. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。 10. 敲木鱼作品架读取当前用户作品列表时走 `GET /api/creation/wooden-fish/works`;发布成功后平台壳必须同时刷新作品架与公开广场,避免作品刚发布时仍停留在旧列表。 11. 移动端草稿页整体禁止长按选择文字,避免误触系统选区;输入框、文本域和可编辑区域仍必须保留文本选择能力。 -发现页 / 推荐页公开作品卡的作者行只显示公开昵称或账号生成的脱敏手机号;不得把纯 `SY-*` 陶泥号或作品号当作卡片作者名。陶泥号搜索、作品号复制和完整作品身份只在搜索、详情页或明确的复制入口展示,避免卡片列表暴露额外账号标识。 +发现页 / 推荐页公开作品卡的作者行只显示可读公开昵称;不得把手机号掩码、账号生成的脱敏手机号、`SY-*` 陶泥号或作品号拼接进卡片作者名。陶泥号搜索、作品号复制和完整作品身份只在搜索、详情页或明确的复制入口展示,避免卡片列表暴露账号标识。推荐页运行态、标题和作者信息必须使用同一套公开作品 key 选中当前条目;新增或补齐公开玩法类型时复用 `buildPlatformPublicGalleryCardKey(...)`,避免运行内容已切换但标题 / 作者仍退回第一条作品。 -发现 Tab、创作 Tab 与草稿 Tab 的页面根内容区不再套 `platform-page-stage` 外层全局卡片壳,让列表、筛选和玩法卡获得更宽的横向空间;推荐页和我的页仍按各自页面设计保留原有全局卡片口径。移动端“我的”页仍按顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口和法律信息组织,不保留旧的底部“填邀请码”次级入口;常用功能当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位;每日任务卡必须读取 `/api/profile/tasks` 的当前任务摘要并在领取后同步刷新卡片进度。字号必须维持平台普通 UI 档位,不能因为窄屏把卡片标题、功能 label 或法律信息撑成展示级字号;最后一屏内容必须能在底部 dock 上方完整滚动露出,不得被固定底部导航遮挡。 +发现 Tab、创作 Tab 与草稿 Tab 的页面根内容区不再套 `platform-page-stage` 外层全局卡片壳,让列表、筛选和玩法卡获得更宽的横向空间;推荐页和我的页仍按各自页面设计保留原有全局卡片口径。移动端“我的”页仍按顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、通用设置入口和法律信息组织,不保留旧的底部“填邀请码”次级入口;主题设置、账号与安全只放在通用设置弹窗下一级,不在外层单独占行;常用功能当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位;每日任务卡必须读取 `/api/profile/tasks` 的当前任务摘要并在领取后同步刷新卡片进度,外层卡片不展示“去完成”等行动按钮。字号必须维持平台普通 UI 档位,不能因为窄屏把卡片标题、功能 label 或法律信息撑成展示级字号;最后一屏内容必须能在底部 dock 上方完整滚动露出,不得被固定底部导航遮挡。 ## RPG / 自定义世界 @@ -166,7 +168,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列 跳一跳作品架走创作中心的统一作品列表:前端通过 `/api/creation/jump-hop/works` 拉取作品摘要,草稿态会与 pending notice 合并后显示在作品架里,已完成但未发布草稿点击后必须通过私有创作接口 `GET /api/creation/jump-hop/works/{profile_id}` 读取完整详情并进入创作结果页;已发布作品点击后才通过公开运行态接口 `GET /api/runtime/jump-hop/works/{profile_id}` 读取完整详情再进入公开详情或运行态,该公开接口保持 published-only 校验。生成中作品仍以后端摘要里的 `generationStatus` 为准,刷新后应能恢复等待遮罩,不能只依赖内存 notice。 -删除等破坏性动作当前未接入 jump-hop 删除 API;如果后续要在作品架提供删除入口,必须先补齐后端/SpacetimeDB/前端整条删除链路,再开放按钮。 +跳一跳作品架删除入口必须走 `/api/creation/jump-hop/works/{profile_id}`,并通过 SpacetimeDB 同步删除 work profile、源 session、运行态 run 与事件,再刷新作品架和公开广场;不得只做前端本地隐藏。 推荐页匿名游玩不再限定为跳一跳。移动端一级 `推荐` Tab 是内嵌运行态刷卡流,会自动选择推荐作品并启动对应玩法;桌面端首页不启动这套移动推荐运行态,而是渲染桌面发现壳,展示 `今日游戏`、`推荐`、`作品分类` 等桌面内容。断点事实统一走 `platformEntryResponsive.ts` 的 `usePlatformDesktopLayout()`,平台壳和首页视图必须共用同一个判断,避免桌面发现页与移动推荐页同时挂载、重复触发请求或启动运行态。移动端推荐页启动或切换作品时先展示当前作品封面,嵌入 runtime 在封面下层加载;只有对应运行态 run / profile 已准备且 lazy runtime 组件完成挂载后,封面才渐隐,不在中途展示“加载中”文案。拼图下一关在同一个 run 内推进到相似作品时不视为推荐作品切换,不能重新显示启动封面。推荐页嵌入运行态启动时按真实身份分流:已登录用户或本地已有 access token 时继续使用账号 Bearer,但请求选项必须是 local auth impact,避免单卡 401 清空整站登录态;只有确认为匿名访客时才申请短期 Runtime Guest Token,并只把它作为局部请求头传给运行态客户端,不写入全局登录态、不触发 refresh,也不把匿名流量伪装成普通用户。当前覆盖矩阵为:跳一跳、视觉小说、抓大鹅 Match3D、方洞挑战、拼图、敲木鱼、大鱼吃小鱼、汪汪声浪。每个模板的启动请求、推荐页内后续运行态动作以及需要上报的 play/finish/leaderboard/next-level 类请求,都必须继续按该身份分流;公开读取入口仍可匿名读取,创作、个人作品、删除、发布、Remix 等账号/所有权动作仍保持普通用户鉴权。 @@ -183,7 +185,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列 创作输入固定为: 1. `敲什么`:敲击物单图资产槽位。默认模板使用内置透明 PNG `/wooden-fish/default-hit-object.png` 作为 `bundled-default` 敲击物资产,避免默认关键词被重新语义化改形;用户输入自定义关键词或上传参考图时,后端必须以默认木鱼图作为基础结构和画风参考,使用 image2 生成最终敲击物图案,上传图只作为新主题参考,不直接进入运行态。自定义 `compile-draft` / `regenerate-hit-object` 必须完成 image2 -> OSS 私有对象 -> asset object 登记和绑定后,再由 `api-server` 注入真实 `hitObjectAsset.imageSrc`,不能只写 `/generated-wooden-fish-assets/...` 占位路径,也不能接受前端请求自带的 `hitObjectAsset` 短路生成。 -2. `敲击音效`:音频资产槽位,当前创作阶段只支持用户上传或麦克风录制;未提供音频时统一写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。提示词生成音效入口临时关闭,通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`;`hitSoundPrompt` 只作为历史兼容字段保留,不参与当前创作流程,也不得由 `spacetime-client` 合成假音频路径。 +2. `敲击音效`:音频资产槽位,当前创作阶段只支持用户上传或麦克风录制;麦克风录制结束后,前端会自动裁掉音频开头连续静音段,再把裁剪后的录音作为 `recorded` 音频资产写入表单。上传音频不做裁剪;浏览器音频解码或裁剪失败时保留原始录音继续保存,不能让用户录音丢失。未提供音频时统一写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。提示词生成音效入口临时关闭,通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`;`hitSoundPrompt` 只作为历史兼容字段保留,不参与当前创作流程,也不得由 `spacetime-client` 合成假音频路径。 3. `功德有什么`:最多 8 条飘字,创作态首屏只保留一个默认词条 `幸运`,其下提供加号格继续追加词条;创作态只保存词条名,运行态飘字展示时再追加 `+1`。运行态顶部总数卡采用品牌化徽标样式,子项计数器预置展示在可展开面板中,未出现词条初始值为 0。 4. `作品标题 / 作品简介 / 主题标签`:不再放在创作工作台首屏,改为生成草稿后的结果页补录区,提交试玩或发布前必须先写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。 @@ -195,6 +197,34 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列 平台首页推荐、精选、最新、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='wooden-fish'` 与 `WF-*` 公开作品号识别敲木鱼作品;公开列表应走 `wooden_fish_gallery_card_view` 订阅缓存,公开详情或运行态启动时卡片摘要不足则补读完整 work profile。 +## 拼消消 + +对外名称:拼消消。工程域与 `playId`:`puzzle-clear`。公开作品码前缀:`PC-`。当前按新增玩法 SOP 接入完整公开闭环,不复用拼图运行态规则本体。 + +链路为: + +```text +创作入口 -> 轻表单工作台 -> 生成过程页 -> 结果页 -> 试玩 -> 发布 -> 统一作品详情 -> 正式运行态 +``` + +工作台字段固定为作品标题、简介、主题词、场地底图主题词 `boardBackgroundPrompt`、中央场地底图槽位、是否 AI 生成底图。中央场地底图必须复用 `CreativeImageInputPanel`,支持上传、历史图和 AI 重绘;若用户填写 `boardBackgroundPrompt`,AI 生成底图只读取该字段,字段为空时才回退读取 `themePrompt`;用户上传底图时不再用主题词重写该资产。中央场地底图的字段名保留平台口径,但实际语义是玩家逐步消除清空棋盘后露出的主题目标图,生成尺寸必须与中央棋盘一致,按 1:1 正方形出图;prompt 必须强绑定主题、画面精致、强表现力并一眼体现主题,不再要求“画面干净”或“适合作为卡牌棋盘底图”。运行态必须把中央场地底图作为棋盘内部静态底图使用,不能降级成整页氛围背景;卡牌消除后产生的空位和拖拽源位应露出该棋盘底图。卡面背面背景 v1 使用默认占位图,不作为创作者配置项。规则参数不开放编辑:单关 `6x6`、每局 10 分钟、35 次目标消除、形状解锁、防死局发牌和半锁定规则均由后端规则集固定。 + +素材生成使用拼消消专用编排,但必须复用 `platform-image`、VectorEngine `gpt-image-2`、OSS、`asset_object`、换签和失败审计。素材目标是 4 张 `1024x1536` 竖版工作表,每张后台按 `4 列 x 6 行` 裁切,每格 `256x256`;服务端从工作表切出总计 95 个 1x1 卡牌碎片,再合成一张 `10x10 / 2560x2560` 最终 atlas。复合图案组总数固定为 35,形状配比固定为 `1x2=23`、`1x3=5`、`2x2=4`、`2x3=3`。服务端先预排每个复合图案组的 sheet 布局、最终 atlas 坐标和形状,再按坐标切成 1x1 卡牌碎片作为运行态素材;sheet 生图 prompt 只能要求复合图案组可按后台 4x6 均等切成 1x1 方形小份,不能让模型在小图案上绘制切分线、边框、网格线、编号或裁切参考线。当前只有单关,同关内复合图案不重复。草稿编译和发布都必须使用 api-server 已持久化的真实 atlas / card assets,拒绝缺失、空对象键或 `placeholder` 占位素材,不允许 `spacetime-client` 或 SpacetimeDB 侧合成临时素材绕过平台图片底座。 + +运行态规则: + +1. 单关固定为 `6x6 / 35次消除`。 +2. 每局固定 10 分钟;超时只判当前关失败,可重试当前关。 +3. 当前关直接出现 `1x2`、`1x3`、`2x2` 和 `2x3`。 +4. 开局棋盘随机铺满并保证至少一步可解;补牌后也必须由后端保证至少一步可解。 +5. 顶部卡牌准备区按纵列补位,某列有空格时该列卡牌从顶部下落。 +6. 非 2 格消除时,补牌不得破坏已完成局部;只有玩家主动交换或撞入才允许打散半锁定拼接组。 +7. 正式 runtime 只消费后端 snapshot 与 action 结果;前端负责开局翻转、拖拽、掉落、消除和弹层动画。 + 拖拽手感必须对齐拼图模板:开局小卡片只翻转一次,交换落位不得重新翻牌;按住后可见卡片立即跟随鼠标或手指,源位置即时留出空槽;放下时被替换卡片要快速飞向对应空位;已完成局部拼接组要以连续整体呈现并可作为整组拖起。拖拽浮层必须挂到页面级 `document.body` portal,避免平台壳层 transform 让 `position: fixed` 和 `clientX/clientY` 坐标系错位。 +8. 正式 `published` run 的终态事件使用 `run-finished` 和 `level-failed`,事件结果 JSON 至少包含 `status`、`level`、`clears`、`clearDelta` 和 `elapsedMs`,供基础统计与排障回读。 + +新增阶段为 `puzzle-clear-workspace`、`puzzle-clear-generating`、`puzzle-clear-result` 和 `puzzle-clear-runtime`;路由为 `/creation/puzzle-clear`、`/creation/puzzle-clear/generating`、`/creation/puzzle-clear/result` 与 `/runtime/puzzle-clear`。API 命名空间为 `/api/creation/puzzle-clear/*` 与 `/api/runtime/puzzle-clear/*`。验证命令见 `docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md` 与 `docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`。 + ## 抓大鹅 Match3D 对外名称:`抓大鹅`。工程域:`match3d`。 diff --git a/docs/【项目基线】当前产品与工程约束-2026-05-15.md b/docs/【项目基线】当前产品与工程约束-2026-05-15.md index 49bdf7b3..8638b222 100644 --- a/docs/【项目基线】当前产品与工程约束-2026-05-15.md +++ b/docs/【项目基线】当前产品与工程约束-2026-05-15.md @@ -49,6 +49,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台。当 6. 小程序外壳注入到 H5 URL 的 `clientType`、`clientRuntime`、`miniProgramEnv` 是宿主上下文,H5 内部 `pushState` / 阶段导航必须跨页面保留,避免登录和充值误判为普通浏览器;首点时微信 JS bridge 可能尚未就绪,前端还需用 `MicroMessenger + miniProgram` User-Agent 作为小程序识别兜底。 7. 小程序 `web-view` 页必须启用好友分享与朋友圈分享,分享目标固定回到 `pages/web-view/index`,不把 H5 当前 URL 作为不受控启动参数传回小程序页。 8. 小程序 `web-view` 外壳运行时通过 `wx.getAccountInfoSync().miniProgram.envVersion` 自动识别版本:线上版 `release` 使用 `www.genarrative.world`,体验版 `trial` 与开发版 `develop` 使用 `dev.genarrative.world`;传给后端的 `x-mini-program-env` 分别为 `release`、`trial`、`dev`。 +9. 账号信息面板只展示 `账号信息` 标题;绑定手机号和绑定微信以紧凑模块展示当前绑定状态,已绑定手机号展示完整手机号,已绑定微信展示微信昵称而不是微信账号标识,换绑入口放在对应模块右上角,退出登录和退出全部设备固定放在面板内容最底部。 ## 账户与充值 @@ -98,9 +99,9 @@ server-rs + Axum + SpacetimeDB 7. 主站入口已锁定移动端页面级缩放;单个游戏页面不要再重复实现整页缩放锁定。 8. 图像输入通用 UI 统一走 `src/components/common/CreativeImageInputPanel.tsx`。外层页面持有业务状态,组件只承担上传卡、预览、参考图缩略图、AI 重绘开关、错误展示和提交按钮。 9. 发现页 `分类` 子频道的筛选必须打开独立 dialog / drawer / modal,至少支持玩法类型过滤与排序切换;筛选结果为空时显示空状态,不把筛选内容展开在当前列表下方。 -10. 移动端“我的”页顶部品牌行承载扫码和设置入口,正文按参考图顺序组织为头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口和法律信息;`media/profile/` 中的陶泥素材作为该页图形资产。常用功能宫格固定承载泥点充值、邀请好友、兑换码、玩家社区、反馈与建议;当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位。页面不再提供独立存档按钮入口,也不在底部保留旧的填邀请码次级入口。填邀请码只由邀请链接 query 或其它明确引导打开独立弹窗,不作为“我的”页常驻按钮。 -11. “我的”页每日任务卡必须展示后端 `/api/profile/tasks` 返回的当前任务摘要,包括奖励泥点数、进度和领取 / 去完成 / 已完成状态;任务领取成功后,卡片摘要必须跟随返回的任务中心数据同步刷新,不能继续硬编码 `0 / 1` 或只更新弹窗内任务列表。 -12. “我的”页泥点、游戏时长、已玩游戏数量三张统计卡只展示各自标签和值,三个统计 icon 使用小尺寸普通 UI 档位,内容不换行,不在统计区底部展示“更新于”时间;移动端昵称、会员卡、每日任务、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。 +10. 移动端“我的”页顶部品牌行承载扫码和设置入口,正文按参考图顺序组织为头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、通用设置入口和法律信息;`media/profile/` 中的陶泥素材作为该页图形资产。常用功能宫格固定承载泥点充值、邀请好友、兑换码、玩家社区、反馈与建议;当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位。页面不再提供独立存档按钮入口,也不在底部保留旧的填邀请码次级入口;主题设置、账号与安全只作为通用设置弹窗下一级入口,不在“我的”页外层单独占行。填邀请码只由邀请链接 query 或其它明确引导打开独立弹窗,不作为“我的”页常驻按钮。 +11. “我的”页每日任务卡必须展示后端 `/api/profile/tasks` 返回的当前任务摘要,包括奖励泥点数和进度;外层任务卡不展示“去完成”等左右侧行动按钮,领取 / 去完成 / 已完成状态只在任务中心弹窗内表达。任务领取成功后,卡片摘要必须跟随返回的任务中心数据同步刷新,不能继续硬编码 `0 / 1` 或只更新弹窗内任务列表。用户停留在“我的”页跨过北京时间 0 点时,前端必须非阻断刷新登录态以补齐 `daily_login` 埋点,再重拉任务中心,避免继续展示上一自然日已领取状态。 +12. “我的”页泥点余额、累计游玩、已玩游戏三张统计卡只展示各自标签和值,三个统计 icon 使用小尺寸普通 UI 档位,内容不换行,不在统计区底部展示“更新于”时间;移动端昵称、会员卡、每日任务、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。 13. 移动端“我的”页需要兼容窄屏:头像 / 昵称 / 陶泥号、三张统计卡、每日任务、五项常用功能和法律信息都必须能在底部固定 TabBar 上方完整滚动露出,不得与底部 dock、刘海 safe-area 或相邻 UI 元素遮挡重叠。 14. RPG 等运行态的战斗飘字、血量变化和即时反馈必须在暗色、噪声高的场景背景上保持可读:使用高亮文字、深色描边、强阴影或小面积半透明底,不只依赖红/绿文字本身表达伤害或治疗。 15. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `src/index.css` 的 `--platform-*` 主题变量和 `apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案。 diff --git a/packages/shared/src/contracts/auth.ts b/packages/shared/src/contracts/auth.ts index 4765cd9f..2a2aa6c6 100644 --- a/packages/shared/src/contracts/auth.ts +++ b/packages/shared/src/contracts/auth.ts @@ -6,10 +6,13 @@ export type AuthUser = { publicUserCode: string; displayName: string; avatarUrl: string | null; + phoneNumber?: string | null; phoneNumberMasked: string | null; loginMethod: AuthLoginMethod; bindingStatus: AuthBindingStatus; wechatBound: boolean; + wechatDisplayName?: string | null; + wechatAccount?: string | null; }; export type PublicUserSummary = { diff --git a/packages/shared/src/contracts/index.ts b/packages/shared/src/contracts/index.ts index c6484f20..5ba8e295 100644 --- a/packages/shared/src/contracts/index.ts +++ b/packages/shared/src/contracts/index.ts @@ -3,6 +3,7 @@ export type * from './creationAudio'; export type * from './hyper3d'; export type * from './jumpHop'; export type * from './puzzleCreativeTemplate'; +export type * from './puzzleClear'; export type * from './publicWork'; export type * from './visualNovel'; export type * from './barkBattle'; diff --git a/packages/shared/src/contracts/puzzleClear.ts b/packages/shared/src/contracts/puzzleClear.ts new file mode 100644 index 00000000..728c93f9 --- /dev/null +++ b/packages/shared/src/contracts/puzzleClear.ts @@ -0,0 +1,226 @@ +export type PuzzleClearGenerationStatus = 'draft' | 'generating' | 'ready' | 'failed'; + +export type PuzzleClearShapeKind = '1x2' | '1x3' | '2x2' | '2x3'; + +export type PuzzleClearOrientation = 'horizontal' | 'vertical'; + +export type PuzzleClearRunStatus = + | 'playing' + | 'level_failed' + | 'level_cleared' + | 'finished'; + +export type PuzzleClearActionType = + | 'compile-draft' + | 'regenerate-atlas' + | 'update-work-meta' + | 'update-board-background'; + +export interface PuzzleClearImageAsset { + assetId: string; + imageSrc: string; + imageObjectKey: string; + assetObjectId: string; + generationProvider: string; + prompt: string; + width: number; + height: number; +} + +export interface PuzzleClearPatternGroup { + groupId: string; + shape: PuzzleClearShapeKind; + width: number; + height: number; + atlasX: number; + atlasY: number; + atlasWidth: number; + atlasHeight: number; +} + +export interface PuzzleClearCardAsset { + cardId: string; + groupId: string; + shape: PuzzleClearShapeKind; + orientation: PuzzleClearOrientation; + partX: number; + partY: number; + imageSrc: string; + imageObjectKey: string; + assetObjectId: string; + sourceAtlasCell: string; +} + +export interface PuzzleClearWorkspaceCreateRequest { + templateId: 'puzzle-clear' | string; + workTitle: string; + workDescription: string; + themePrompt: string; + boardBackgroundPrompt: string; + generateBoardBackground: boolean; + boardBackgroundAsset?: PuzzleClearImageAsset | null; +} + +export interface PuzzleClearActionRequest { + actionType: PuzzleClearActionType; + profileId?: string | null; + workTitle?: string | null; + workDescription?: string | null; + themePrompt?: string | null; + boardBackgroundPrompt?: string | null; + generateBoardBackground?: boolean | null; + boardBackgroundAsset?: PuzzleClearImageAsset | null; + atlasAsset?: PuzzleClearImageAsset | null; + patternGroups?: PuzzleClearPatternGroup[] | null; + cardAssets?: PuzzleClearCardAsset[] | null; +} + +export interface PuzzleClearDraftResponse { + templateId: string; + templateName: string; + profileId: string | null; + workTitle: string; + workDescription: string; + themePrompt: string; + boardBackgroundPrompt: string; + generateBoardBackground: boolean; + boardBackgroundAsset: PuzzleClearImageAsset | null; + cardBackImageSrc: string | null; + atlasAsset: PuzzleClearImageAsset | null; + patternGroups: PuzzleClearPatternGroup[]; + cardAssets: PuzzleClearCardAsset[]; + generationStatus: PuzzleClearGenerationStatus; +} + +export interface PuzzleClearSessionSnapshotResponse { + sessionId: string; + ownerUserId: string; + status: PuzzleClearGenerationStatus; + draft: PuzzleClearDraftResponse | null; + createdAt: string; + updatedAt: string; +} + +export interface PuzzleClearSessionResponse { + session: PuzzleClearSessionSnapshotResponse; +} + +export interface PuzzleClearActionResponse { + actionType: PuzzleClearActionType; + session: PuzzleClearSessionSnapshotResponse; + work: PuzzleClearWorkProfileResponse | null; +} + +export interface PuzzleClearWorkSummaryResponse { + runtimeKind: 'puzzle-clear'; + workId: string; + profileId: string; + ownerUserId: string; + sourceSessionId: string | null; + workTitle: string; + workDescription: string; + themePrompt: string; + coverImageSrc: string | null; + publicationStatus: string; + playCount: number; + updatedAt: string; + publishedAt: string | null; + publishReady: boolean; + generationStatus: PuzzleClearGenerationStatus; +} + +export interface PuzzleClearWorkProfileResponse { + summary: PuzzleClearWorkSummaryResponse; + draft: PuzzleClearDraftResponse; + boardBackgroundAsset: PuzzleClearImageAsset | null; + atlasAsset: PuzzleClearImageAsset; + patternGroups: PuzzleClearPatternGroup[]; + cardAssets: PuzzleClearCardAsset[]; +} + +export interface PuzzleClearWorksResponse { + items: PuzzleClearWorkSummaryResponse[]; +} + +export interface PuzzleClearWorkDetailResponse { + item: PuzzleClearWorkProfileResponse; +} + +export interface PuzzleClearWorkMutationResponse { + item: PuzzleClearWorkProfileResponse; +} + +export interface PuzzleClearGalleryCardResponse + extends PuzzleClearWorkSummaryResponse { + publicWorkCode?: string; + authorDisplayName?: string; + recentPlayCount7d?: number; +} + +export interface PuzzleClearGalleryResponse { + items: PuzzleClearGalleryCardResponse[]; + hasMore: boolean; + nextCursor: string | null; +} + +export interface PuzzleClearGalleryDetailResponse { + item: PuzzleClearWorkProfileResponse; +} + +export interface PuzzleClearBoardCell { + row: number; + col: number; + card: PuzzleClearCardAsset | null; + lockedGroupId: string | null; +} + +export interface PuzzleClearBoardSnapshot { + rows: number; + cols: number; + cells: PuzzleClearBoardCell[]; +} + +export interface PuzzleClearRuntimeSnapshotResponse { + runId: string; + profileId: string; + ownerUserId: string; + runtimeMode?: 'draft' | 'published'; + status: PuzzleClearRunStatus; + levelIndex: number; + clearsDone: number; + targetClears: number; + levelDurationSeconds: number; + levelStartedAtMs: number; + board: PuzzleClearBoardSnapshot; + readyColumns: PuzzleClearCardAsset[][]; + startedAtMs: number; + finishedAtMs: number | null; +} + +export interface PuzzleClearRunResponse { + run: PuzzleClearRuntimeSnapshotResponse; +} + +export interface PuzzleClearStartRunRequest { + profileId: string; +} + +export interface PuzzleClearSwapRequest { + fromRow: number; + fromCol: number; + toRow: number; + toCol: number; + clientActionId: string; +} + +export interface PuzzleClearRetryLevelRequest { + clientActionId: string; +} + +export interface PuzzleClearNextLevelRequest { + clientActionId: string; +} + +export interface PuzzleClearTimeUpRequest { + clientActionId: string; +} diff --git a/scripts/check-spacetime-schema-guard.mjs b/scripts/check-spacetime-schema-guard.mjs index 6f72ac8d..a935012a 100644 --- a/scripts/check-spacetime-schema-guard.mjs +++ b/scripts/check-spacetime-schema-guard.mjs @@ -477,8 +477,14 @@ function getChangedFiles(baseRef) { const diffOutput = tryGit(['diff', '--name-only', '-z', baseRef, '--']) ?? ''; const untrackedOutput = tryGit(['ls-files', '--others', '--exclude-standard', '-z', moduleSrcRoot]) ?? ''; + const untrackedBindingsOutput = + tryGit(['ls-files', '--others', '--exclude-standard', '-z', bindingsRoot]) ?? ''; return new Set( - [...diffOutput.split(/\u0000/u), ...untrackedOutput.split(/\u0000/u)] + [ + ...diffOutput.split(/\u0000/u), + ...untrackedOutput.split(/\u0000/u), + ...untrackedBindingsOutput.split(/\u0000/u), + ] .map(normalizePath) .filter(Boolean), ); diff --git a/scripts/dev-utils.test.ts b/scripts/dev-utils.test.ts index aeabfcee..b027b525 100644 --- a/scripts/dev-utils.test.ts +++ b/scripts/dev-utils.test.ts @@ -88,6 +88,29 @@ describe('dev utils env merge', () => { ); }); + test('本地短信 smoke 可以用 mock 验证码覆盖真实短信 provider 口径', () => { + withTempEnvFiles( + { + '.env.local': [ + 'SMS_AUTH_ENABLED=true', + 'SMS_AUTH_PROVIDER=mock', + 'SMS_AUTH_MOCK_VERIFY_CODE=123456', + ].join('\n'), + }, + (_env, tempDir) => { + const env = mergeApiServerEnv(tempDir, { + SMS_AUTH_ENABLED: 'true', + SMS_AUTH_PROVIDER: 'aliyun', + SMS_AUTH_MOCK_VERIFY_CODE: '654321', + }); + + expect(env.SMS_AUTH_ENABLED).toBe('true'); + expect(env.SMS_AUTH_PROVIDER).toBe('mock'); + expect(env.SMS_AUTH_MOCK_VERIFY_CODE).toBe('123456'); + }, + ); + }); + test('空外层 shell 变量不会遮蔽本地私密配置', () => { withTempEnvFiles( { diff --git a/scripts/dev.mjs b/scripts/dev.mjs index b45c06f6..6e8363a5 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -5,6 +5,7 @@ import { existsSync, readdirSync, readFileSync, + realpathSync, statSync, watch, writeFileSync, @@ -2046,6 +2047,36 @@ function normalizePath(path) { return path.replace(/\\/gu, '/'); } +function normalizeDirectExecutionPath(path) { + return normalizePath(path).replace(/^\/([A-Za-z]:\/)/u, '$1'); +} + +function safeRealpath(pathValue) { + try { + return realpathSync(pathValue); + } catch { + return resolve(pathValue); + } +} + +function isDirectModuleExecution(argv1, moduleUrl, resolvePath = safeRealpath) { + if (!argv1) { + return false; + } + + try { + return ( + normalizeDirectExecutionPath(resolvePath(argv1)) === + normalizeDirectExecutionPath(resolvePath(fileURLToPath(moduleUrl))) + ); + } catch { + return ( + normalizeDirectExecutionPath(resolve(argv1)) === + normalizeDirectExecutionPath(fileURLToPath(moduleUrl)) + ); + } +} + function buildSpacetimePublishArgs({database, server, preserveDatabase}) { const args = [ 'publish', @@ -2098,6 +2129,7 @@ export { createDevServerSpawnOptions, createWatchConfigs, isSpacetimePublishPermissionError, + isDirectModuleExecution, parseSpacetimeToolVersion, parseArgs, resolveDevStackStatePath, @@ -2129,6 +2161,6 @@ async function main() { } } -if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { +if (isDirectModuleExecution(process.argv[1], import.meta.url)) { void main(); } diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index b22bad82..9e6a7ef5 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -13,6 +13,7 @@ import { buildSpacetimePublishArgs, createDevServerSpawnOptions, createWatchConfigs, + isDirectModuleExecution, isSpacetimePublishPermissionError, parseSpacetimeToolVersion, parseArgs, @@ -39,6 +40,19 @@ function workspaceSpacetimeVersionForTest() { describe('dev scheduler argument routing', () => { const linuxTest = process.platform === 'linux' ? test : test.skip; + test('Windows junction 路径下的直接执行入口也能识别为当前模块', () => { + const moduleUrl = + 'file:///F:/DevWorktrees/codex/worktrees/f584/Genarrative/scripts/dev.mjs'; + const argv1 = + 'C:\\Users\\wuxiangwanzi\\.codex\\worktrees\\f584\\Genarrative\\scripts\\dev.mjs'; + const resolvePath = (value) => + value.startsWith('C:\\Users\\') + ? 'F:\\DevWorktrees\\codex\\worktrees\\f584\\Genarrative\\scripts\\dev.mjs' + : value; + + expect(isDirectModuleExecution(argv1, moduleUrl, resolvePath)).toBe(true); + }); + test('完整 dev 栈覆盖前端代理到本次解析出的 api-server 地址', () => { const {command, explicitOptions, options} = parseArgs([], { GENARRATIVE_API_PORT: '8090', diff --git a/server-rs/Cargo.lock b/server-rs/Cargo.lock index 5b149323..14a00146 100644 --- a/server-rs/Cargo.lock +++ b/server-rs/Cargo.lock @@ -113,6 +113,7 @@ dependencies = [ "module-match3d", "module-npc", "module-puzzle", + "module-puzzle-clear", "module-runtime", "module-runtime-item", "module-runtime-story", @@ -1971,6 +1972,15 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "module-puzzle-clear" +version = "0.1.0" +dependencies = [ + "serde", + "shared-kernel", + "spacetimedb", +] + [[package]] name = "module-quest" version = "0.1.0" @@ -3416,6 +3426,7 @@ dependencies = [ "module-match3d", "module-npc", "module-puzzle", + "module-puzzle-clear", "module-runtime", "module-runtime-item", "module-runtime-story", @@ -3451,6 +3462,7 @@ dependencies = [ "module-npc", "module-progression", "module-puzzle", + "module-puzzle-clear", "module-quest", "module-runtime", "module-runtime-item", diff --git a/server-rs/Cargo.toml b/server-rs/Cargo.toml index fdb306a3..82abcab1 100644 --- a/server-rs/Cargo.toml +++ b/server-rs/Cargo.toml @@ -22,6 +22,7 @@ members = [ "crates/module-match3d", "crates/module-npc", "crates/module-puzzle", + "crates/module-puzzle-clear", "crates/module-progression", "crates/module-quest", "crates/module-runtime", @@ -68,6 +69,7 @@ module-match3d = { path = "crates/module-match3d", default-features = false } module-npc = { path = "crates/module-npc", default-features = false } module-progression = { path = "crates/module-progression", default-features = false } module-puzzle = { path = "crates/module-puzzle", default-features = false } +module-puzzle-clear = { path = "crates/module-puzzle-clear", default-features = false } module-quest = { path = "crates/module-quest", default-features = false } module-runtime = { path = "crates/module-runtime", default-features = false } module-runtime-item = { path = "crates/module-runtime-item", default-features = false } diff --git a/server-rs/crates/api-server/Cargo.toml b/server-rs/crates/api-server/Cargo.toml index 28b54d93..0374defc 100644 --- a/server-rs/crates/api-server/Cargo.toml +++ b/server-rs/crates/api-server/Cargo.toml @@ -29,6 +29,7 @@ module-inventory = { workspace = true } module-match3d = { workspace = true } module-npc = { workspace = true } module-puzzle = { workspace = true } +module-puzzle-clear = { workspace = true } module-runtime = { workspace = true } module-runtime-story = { workspace = true } module-runtime-item = { workspace = true } diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index 5fe098a3..248c09d1 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -67,6 +67,7 @@ pub fn build_router(state: AppState) -> Router { .merge(modules::jump_hop::router(state.clone())) .merge(modules::wooden_fish::router(state.clone())) .merge(modules::public_work::router(state.clone())) + .merge(modules::puzzle_clear::router(state.clone())) .merge(modules::puzzle::router(state.clone())) .merge(visual_novel_router(state.clone())) .route( @@ -2697,6 +2698,18 @@ mod tests { bind_payload["user"]["phoneNumberMasked"], Value::String("138****8000".to_string()) ); + assert_eq!( + bind_payload["user"]["phoneNumber"], + Value::String("+8613800138000".to_string()) + ); + assert_eq!( + bind_payload["user"]["wechatAccount"], + Value::String("wx-mini-code-bind-001".to_string()) + ); + assert_eq!( + bind_payload["user"]["wechatDisplayName"], + Value::String("微信旅人".to_string()) + ); assert!( bind_payload["token"] .as_str() @@ -3384,6 +3397,10 @@ mod tests { serde_json::from_slice(&body).expect("response body should be valid json"); assert_eq!(payload["user"]["id"], Value::String(seed_user.id)); + assert_eq!( + payload["user"]["phoneNumber"], + Value::String("+8613800138016".to_string()) + ); assert_eq!( payload["availableLoginMethods"], serde_json::json!(["phone", "password", "wechat"]) @@ -4119,8 +4136,7 @@ mod tests { .await .expect("banners body should collect") .to_bytes(); - let payload: Value = - serde_json::from_slice(&body).expect("banners payload should be json"); + let payload: Value = serde_json::from_slice(&body).expect("banners payload should be json"); assert_eq!(payload["eventBanners"][0]["title"], "后台表单公告"); assert_eq!(payload["eventBanners"][0]["renderMode"], "html"); diff --git a/server-rs/crates/api-server/src/auth_payload.rs b/server-rs/crates/api-server/src/auth_payload.rs index c4cc8673..4c2a6242 100644 --- a/server-rs/crates/api-server/src/auth_payload.rs +++ b/server-rs/crates/api-server/src/auth_payload.rs @@ -7,10 +7,13 @@ pub fn map_auth_user_payload(user: AuthUser) -> AuthUserPayload { public_user_code: user.public_user_code, display_name: user.display_name, avatar_url: user.avatar_url, + phone_number: user.phone_number, phone_number_masked: user.phone_number_masked, login_method: user.login_method.as_str().to_string(), binding_status: user.binding_status.as_str().to_string(), wechat_bound: user.wechat_bound, + wechat_display_name: user.wechat_display_name, + wechat_account: user.wechat_account, } } diff --git a/server-rs/crates/api-server/src/bark_battle.rs b/server-rs/crates/api-server/src/bark_battle.rs index f89affce..56cc47e7 100644 --- a/server-rs/crates/api-server/src/bark_battle.rs +++ b/server-rs/crates/api-server/src/bark_battle.rs @@ -30,7 +30,7 @@ use shared_kernel::{ use spacetime_client::{ BarkBattleDraftConfigUpsertRecordInput, BarkBattleDraftCreateRecordInput, BarkBattleRunFinishRecordInput, BarkBattleRunRecord, BarkBattleRunStartRecordInput, - BarkBattleWorkPublishRecordInput, SpacetimeClientError, + BarkBattleWorkDeleteRecordInput, BarkBattleWorkPublishRecordInput, SpacetimeClientError, }; use time::{Duration as TimeDuration, OffsetDateTime}; @@ -406,6 +406,38 @@ pub async fn list_bark_battle_works( )) } +pub async fn delete_bark_battle_work( + State(state): State, + Path(work_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &work_id, "workId")?; + let items = state + .spacetime_client() + .delete_bark_battle_work(BarkBattleWorkDeleteRecordInput { + work_id, + owner_user_id: authenticated.claims().user_id().to_string(), + }) + .await + .map_err(|error| { + bark_battle_error_response(&request_context, map_bark_battle_client_error(error)) + })?; + let items = items + .into_iter() + .map(|item| { + let author_display_name = + resolve_bark_battle_author_display_name_for_record(&state, &item); + map_work_summary_record(item, &request_context, author_display_name) + }) + .collect::, _>>()?; + + Ok(json_success_body( + Some(&request_context), + BarkBattleWorksResponse { items }, + )) +} + pub async fn list_bark_battle_gallery( State(state): State, Extension(request_context): Extension, diff --git a/server-rs/crates/api-server/src/creation_entry_config.rs b/server-rs/crates/api-server/src/creation_entry_config.rs index 5089422a..81f95e93 100644 --- a/server-rs/crates/api-server/src/creation_entry_config.rs +++ b/server-rs/crates/api-server/src/creation_entry_config.rs @@ -77,17 +77,13 @@ pub fn resolve_creation_entry_route_id(path: &str) -> Option<&'static str> { { return Some("puzzle"); } - if normalized.starts_with("/api/runtime/puzzle/gallery/") - && normalized.ends_with("/remix") - { + if normalized.starts_with("/api/runtime/puzzle/gallery/") && normalized.ends_with("/remix") { return Some("puzzle"); } if normalized == "/api/runtime/big-fish/agent/sessions" { return Some("big-fish"); } - if normalized.starts_with("/api/runtime/big-fish/gallery/") - && normalized.ends_with("/remix") - { + if normalized.starts_with("/api/runtime/big-fish/gallery/") && normalized.ends_with("/remix") { return Some("big-fish"); } if normalized == "/api/runtime/custom-world/agent/sessions" @@ -115,6 +111,9 @@ pub fn resolve_creation_entry_route_id(path: &str) -> Option<&'static str> { if normalized == "/api/creation/jump-hop/sessions" { return Some("jump-hop"); } + if normalized == "/api/creation/puzzle-clear/sessions" { + return Some("puzzle-clear"); + } if normalized == "/api/creation/visual-novel/sessions" { return Some("visual-novel"); } @@ -178,6 +177,10 @@ mod tests { resolve_creation_entry_route_id("/api/runtime/puzzle/agent/sessions"), Some("puzzle"), ); + assert_eq!( + resolve_creation_entry_route_id("/api/creation/puzzle-clear/sessions"), + Some("puzzle-clear"), + ); assert_eq!( resolve_creation_entry_route_id("/api/runtime/puzzle/gallery/profile-1/remix"), Some("puzzle"), @@ -236,6 +239,10 @@ mod tests { resolve_creation_entry_route_id("/api/runtime/wooden-fish/runs/run-1"), None, ); + assert_eq!( + resolve_creation_entry_route_id("/api/runtime/puzzle-clear/runs/run-1"), + None, + ); assert_eq!( resolve_creation_entry_route_id("/api/creation/wooden-fish/sessions"), Some("wooden-fish"), diff --git a/server-rs/crates/api-server/src/http_error.rs b/server-rs/crates/api-server/src/http_error.rs index 85699b70..ac061d6d 100644 --- a/server-rs/crates/api-server/src/http_error.rs +++ b/server-rs/crates/api-server/src/http_error.rs @@ -42,6 +42,10 @@ impl AppError { &self.message } + pub fn details(&self) -> Option<&Value> { + self.details.as_ref() + } + pub fn body_text(&self) -> String { // 批处理任务不能只读 HTTP 状态文案,否则 DashScope 返回的真实失败原因会被压成“上游服务请求失败”。 self.details diff --git a/server-rs/crates/api-server/src/jump_hop.rs b/server-rs/crates/api-server/src/jump_hop.rs index 76649e17..c1372fc3 100644 --- a/server-rs/crates/api-server/src/jump_hop.rs +++ b/server-rs/crates/api-server/src/jump_hop.rs @@ -250,6 +250,36 @@ pub async fn get_jump_hop_work_detail( )) } +pub async fn delete_jump_hop_work( + State(state): State, + Path(profile_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &profile_id, "profileId")?; + let works = state + .spacetime_client() + .delete_jump_hop_work( + profile_id, + authenticated.claims().user_id().to_string(), + ) + .await + .map_err(|error| { + jump_hop_error_response( + &request_context, + JUMP_HOP_CREATION_PROVIDER, + map_jump_hop_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + JumpHopWorksResponse { + items: works.into_iter().map(|work| work.summary).collect(), + }, + )) +} + pub async fn get_jump_hop_runtime_work( State(state): State, Path(profile_id): Path, @@ -311,7 +341,10 @@ pub async fn start_jump_hop_run( ) -> Result, Response> { let Json(payload) = jump_hop_json(payload, &request_context, JUMP_HOP_RUNTIME_PROVIDER)?; ensure_non_empty(&request_context, &payload.profile_id, "profileId")?; - let is_draft_runtime = payload.runtime_mode.as_deref() == Some("draft"); + let is_draft_runtime = payload + .runtime_mode + .as_deref() + .is_some_and(is_jump_hop_draft_runtime_mode); let owner_user_id = principal.subject().to_string(); let principal_kind = principal.kind().as_str(); let run = state @@ -1268,6 +1301,10 @@ fn build_jump_hop_work_play_tracking_draft( WorkPlayTrackingDraft::runtime_principal("jump-hop", work_id, principal, source_route) } +fn is_jump_hop_draft_runtime_mode(runtime_mode: &str) -> bool { + runtime_mode.trim().eq_ignore_ascii_case("draft") +} + fn build_jump_hop_draft(payload: &JumpHopWorkspaceCreateRequest) -> JumpHopDraftResponse { let theme_text = normalize_theme_text(&payload.theme_text, &payload.work_title); JumpHopDraftResponse { @@ -1450,6 +1487,14 @@ fn current_utc_micros() -> i64 { mod tests { use super::*; + #[test] + fn jump_hop_draft_runtime_mode_detection_matches_client_normalization() { + assert!(is_jump_hop_draft_runtime_mode("draft")); + assert!(is_jump_hop_draft_runtime_mode(" DRAFT ")); + assert!(!is_jump_hop_draft_runtime_mode("published")); + assert!(!is_jump_hop_draft_runtime_mode("")); + } + #[test] fn jump_hop_tile_atlas_prompt_uses_dedicated_five_by_five_floor_layout() { let prompt = build_jump_hop_tile_atlas_prompt("森林冒险", "森林主题清爽游戏化立体感平台"); diff --git a/server-rs/crates/api-server/src/main.rs b/server-rs/crates/api-server/src/main.rs index fc9ee2e4..bb1098de 100644 --- a/server-rs/crates/api-server/src/main.rs +++ b/server-rs/crates/api-server/src/main.rs @@ -64,6 +64,7 @@ mod prompt; mod public_work; mod puzzle; mod puzzle_agent_turn; +mod puzzle_clear; mod puzzle_gallery_cache; mod refresh_session; mod registration_reward; diff --git a/server-rs/crates/api-server/src/modules.rs b/server-rs/crates/api-server/src/modules.rs index 9d643493..1cac08d9 100644 --- a/server-rs/crates/api-server/src/modules.rs +++ b/server-rs/crates/api-server/src/modules.rs @@ -13,6 +13,7 @@ pub mod platform; pub mod profile; pub mod public_work; pub mod puzzle; +pub mod puzzle_clear; pub mod square_hole; pub mod story; pub mod wooden_fish; diff --git a/server-rs/crates/api-server/src/modules/bark_battle.rs b/server-rs/crates/api-server/src/modules/bark_battle.rs index 14dac1ae..6184150e 100644 --- a/server-rs/crates/api-server/src/modules/bark_battle.rs +++ b/server-rs/crates/api-server/src/modules/bark_battle.rs @@ -1,15 +1,15 @@ use axum::{ Router, middleware, - routing::{get, post}, + routing::{delete, get, post}, }; use crate::{ auth::require_bearer_auth, bark_battle::{ - create_bark_battle_draft, finish_bark_battle_run, generate_bark_battle_image_asset, - get_bark_battle_run, get_bark_battle_runtime_config, list_bark_battle_gallery, - list_bark_battle_works, publish_bark_battle_work, start_bark_battle_run, - update_bark_battle_draft_config, + create_bark_battle_draft, delete_bark_battle_work, finish_bark_battle_run, + generate_bark_battle_image_asset, get_bark_battle_run, get_bark_battle_runtime_config, + list_bark_battle_gallery, list_bark_battle_works, publish_bark_battle_work, + start_bark_battle_run, update_bark_battle_draft_config, }, state::AppState, }; @@ -51,6 +51,13 @@ pub fn router(state: AppState) -> Router { require_bearer_auth, )), ) + .route( + "/api/runtime/bark-battle/works/{work_id}", + delete(delete_bark_battle_work).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) .route( "/api/runtime/bark-battle/gallery", get(list_bark_battle_gallery), diff --git a/server-rs/crates/api-server/src/modules/jump_hop.rs b/server-rs/crates/api-server/src/modules/jump_hop.rs index d051d052..2ed65a3b 100644 --- a/server-rs/crates/api-server/src/modules/jump_hop.rs +++ b/server-rs/crates/api-server/src/modules/jump_hop.rs @@ -1,16 +1,17 @@ use axum::{ - Router, middleware, + middleware, routing::{get, post}, + Router, }; use crate::{ auth::{require_bearer_auth, require_runtime_principal_auth}, jump_hop::{ - create_jump_hop_session, execute_jump_hop_action, get_jump_hop_gallery_detail, - get_jump_hop_leaderboard, get_jump_hop_runtime_work, get_jump_hop_session, - get_jump_hop_work_detail, - jump_hop_run_jump, list_jump_hop_gallery, list_jump_hop_works, publish_jump_hop_work, - restart_jump_hop_run, start_jump_hop_run, + create_jump_hop_session, delete_jump_hop_work, execute_jump_hop_action, + get_jump_hop_gallery_detail, get_jump_hop_leaderboard, get_jump_hop_runtime_work, + get_jump_hop_session, get_jump_hop_work_detail, jump_hop_run_jump, + list_jump_hop_gallery, list_jump_hop_works, publish_jump_hop_work, restart_jump_hop_run, + start_jump_hop_run, }, state::AppState, }; @@ -47,10 +48,12 @@ pub fn router(state: AppState) -> Router { ) .route( "/api/creation/jump-hop/works/{profile_id}", - get(get_jump_hop_work_detail).route_layer(middleware::from_fn_with_state( - state.clone(), - require_bearer_auth, - )), + get(get_jump_hop_work_detail) + .delete(delete_jump_hop_work) + .route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), ) .route( "/api/creation/jump-hop/works/{profile_id}/publish", diff --git a/server-rs/crates/api-server/src/modules/puzzle_clear.rs b/server-rs/crates/api-server/src/modules/puzzle_clear.rs new file mode 100644 index 00000000..3eaabde6 --- /dev/null +++ b/server-rs/crates/api-server/src/modules/puzzle_clear.rs @@ -0,0 +1,116 @@ +use axum::{ + Router, middleware, + routing::{get, post}, +}; + +use crate::{ + auth::{require_bearer_auth, require_runtime_principal_auth}, + puzzle_clear::{ + advance_puzzle_clear_next_level, create_puzzle_clear_session, execute_puzzle_clear_action, + get_puzzle_clear_gallery_detail, get_puzzle_clear_run, get_puzzle_clear_runtime_work, + get_puzzle_clear_session, get_puzzle_clear_work, list_puzzle_clear_gallery, + list_puzzle_clear_works, mark_puzzle_clear_level_time_up, publish_puzzle_clear_work, + retry_puzzle_clear_level, start_puzzle_clear_run, swap_puzzle_clear_cards, + }, + state::AppState, +}; + +pub fn router(state: AppState) -> Router { + Router::new() + .route( + "/api/creation/puzzle-clear/sessions", + post(create_puzzle_clear_session).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/creation/puzzle-clear/sessions/{session_id}", + get(get_puzzle_clear_session).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/creation/puzzle-clear/sessions/{session_id}/actions", + post(execute_puzzle_clear_action).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/creation/puzzle-clear/works", + get(list_puzzle_clear_works).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/creation/puzzle-clear/works/{profile_id}", + get(get_puzzle_clear_work).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/creation/puzzle-clear/works/{profile_id}/publish", + post(publish_puzzle_clear_work).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/works/{profile_id}", + get(get_puzzle_clear_runtime_work), + ) + .route( + "/api/runtime/puzzle-clear/runs", + post(start_puzzle_clear_run).route_layer(middleware::from_fn_with_state( + state.clone(), + require_runtime_principal_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/runs/{run_id}", + get(get_puzzle_clear_run).route_layer(middleware::from_fn_with_state( + state.clone(), + require_runtime_principal_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/runs/{run_id}/swap", + post(swap_puzzle_clear_cards).route_layer(middleware::from_fn_with_state( + state.clone(), + require_runtime_principal_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/runs/{run_id}/retry-level", + post(retry_puzzle_clear_level).route_layer(middleware::from_fn_with_state( + state.clone(), + require_runtime_principal_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/runs/{run_id}/next-level", + post(advance_puzzle_clear_next_level).route_layer(middleware::from_fn_with_state( + state.clone(), + require_runtime_principal_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/runs/{run_id}/time-up", + post(mark_puzzle_clear_level_time_up).route_layer(middleware::from_fn_with_state( + state.clone(), + require_runtime_principal_auth, + )), + ) + .route( + "/api/runtime/puzzle-clear/gallery", + get(list_puzzle_clear_gallery), + ) + .route( + "/api/runtime/puzzle-clear/gallery/{public_work_code}", + get(get_puzzle_clear_gallery_detail), + ) +} diff --git a/server-rs/crates/api-server/src/modules/wooden_fish.rs b/server-rs/crates/api-server/src/modules/wooden_fish.rs index 556c31b0..b46c4750 100644 --- a/server-rs/crates/api-server/src/modules/wooden_fish.rs +++ b/server-rs/crates/api-server/src/modules/wooden_fish.rs @@ -1,16 +1,16 @@ use axum::{ Router, middleware, - routing::{get, post}, + routing::{delete, get, post}, }; use crate::{ auth::{require_bearer_auth, require_runtime_principal_auth}, state::AppState, wooden_fish::{ - checkpoint_wooden_fish_run, create_wooden_fish_session, execute_wooden_fish_action, - finish_wooden_fish_run, get_wooden_fish_gallery_detail, get_wooden_fish_runtime_work, - get_wooden_fish_session, list_wooden_fish_gallery, list_wooden_fish_works, - publish_wooden_fish_work, start_wooden_fish_run, + checkpoint_wooden_fish_run, create_wooden_fish_session, delete_wooden_fish_work, + execute_wooden_fish_action, finish_wooden_fish_run, get_wooden_fish_gallery_detail, + get_wooden_fish_runtime_work, get_wooden_fish_session, list_wooden_fish_gallery, + list_wooden_fish_works, publish_wooden_fish_work, start_wooden_fish_run, }, }; @@ -44,6 +44,13 @@ pub fn router(state: AppState) -> Router { require_bearer_auth, )), ) + .route( + "/api/creation/wooden-fish/works/{profile_id}", + delete(delete_wooden_fish_work).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) .route( "/api/creation/wooden-fish/works/{profile_id}/publish", post(publish_wooden_fish_work).route_layer(middleware::from_fn_with_state( diff --git a/server-rs/crates/api-server/src/puzzle.rs b/server-rs/crates/api-server/src/puzzle.rs index b4fe7b41..be4b8cb0 100644 --- a/server-rs/crates/api-server/src/puzzle.rs +++ b/server-rs/crates/api-server/src/puzzle.rs @@ -1,5 +1,6 @@ use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashSet}, + sync::{Mutex, OnceLock}, time::{Instant, SystemTime, UNIX_EPOCH}, }; @@ -130,6 +131,73 @@ const PUZZLE_UI_BACKGROUND_PROMPT_FALLBACK_MARKER: &str = const PUZZLE_VECTOR_ENGINE_SQUARE_IMAGE_SIZE: &str = "1024x1024"; const PUZZLE_VECTOR_ENGINE_PORTRAIT_IMAGE_SIZE: &str = "1024x1536"; +static PUZZLE_BACKGROUND_COMPILE_TASKS: OnceLock>> = OnceLock::new(); + +fn puzzle_background_compile_tasks() -> &'static Mutex> { + PUZZLE_BACKGROUND_COMPILE_TASKS.get_or_init(|| Mutex::new(HashSet::new())) +} + +fn try_register_puzzle_background_compile_task(session_id: &str) -> bool { + match puzzle_background_compile_tasks().lock() { + Ok(mut tasks) => tasks.insert(session_id.to_string()), + Err(error) => { + tracing::warn!( + provider = PUZZLE_AGENT_API_BASE_PROVIDER, + session_id, + error = %error, + "拼图后台生成任务注册表锁已损坏,允许本次任务继续" + ); + true + } + } +} + +fn unregister_puzzle_background_compile_task(session_id: &str) { + match puzzle_background_compile_tasks().lock() { + Ok(mut tasks) => { + tasks.remove(session_id); + } + Err(error) => { + tracing::warn!( + provider = PUZZLE_AGENT_API_BASE_PROVIDER, + session_id, + error = %error, + "拼图后台生成任务注册表解锁失败,忽略清理" + ); + } + } +} + +fn has_puzzle_cover_image_src(value: &Option) -> bool { + value + .as_deref() + .map(str::trim) + .is_some_and(|value| !value.is_empty()) +} + +fn mark_puzzle_initial_generation_started_snapshot( + mut session: PuzzleAgentSessionRecord, +) -> PuzzleAgentSessionRecord { + session.stage = "image_refining".to_string(); + session.progress_percent = session.progress_percent.max(88); + if let Some(draft) = session.draft.as_mut() { + let draft_needs_cover = !has_puzzle_cover_image_src(&draft.cover_image_src); + if let Some(primary_level) = draft.levels.first_mut() { + if !has_puzzle_cover_image_src(&primary_level.cover_image_src) { + primary_level.generation_status = "generating".to_string(); + } + draft.generation_status = primary_level.generation_status.clone(); + draft.candidates = primary_level.candidates.clone(); + draft.selected_candidate_id = primary_level.selected_candidate_id.clone(); + draft.cover_image_src = primary_level.cover_image_src.clone(); + draft.cover_asset_id = primary_level.cover_asset_id.clone(); + } else if draft_needs_cover { + draft.generation_status = "generating".to_string(); + } + } + session +} + pub(crate) fn format_puzzle_reference_image_upload_bytes(bytes: usize) -> String { format!("{:.1}MB", bytes as f64 / 1024.0 / 1024.0) } diff --git a/server-rs/crates/api-server/src/puzzle/draft.rs b/server-rs/crates/api-server/src/puzzle/draft.rs index 276a29f5..43bc146d 100644 --- a/server-rs/crates/api-server/src/puzzle/draft.rs +++ b/server-rs/crates/api-server/src/puzzle/draft.rs @@ -1177,21 +1177,16 @@ pub(crate) fn find_puzzle_level_for_initial_asset_check<'a>( .or_else(|| levels.first()) } -pub(crate) async fn compile_puzzle_draft_with_initial_cover( +pub(crate) async fn generate_puzzle_initial_cover_from_compiled_session( state: &PuzzleApiState, request_context: &RequestContext, - session_id: String, + compiled_session: PuzzleAgentSessionRecord, owner_user_id: String, prompt_text: Option<&str>, reference_image_src: Option<&str>, image_model: Option<&str>, now: i64, ) -> Result { - let compiled_session = state - .spacetime_client() - .compile_puzzle_agent_draft(session_id.clone(), owner_user_id.clone(), now) - .await - .map_err(map_puzzle_compile_error)?; let draft = compiled_session.draft.clone().ok_or_else(|| { AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ "provider": PUZZLE_AGENT_API_BASE_PROVIDER, @@ -1419,7 +1414,7 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover( match state .spacetime_client() .select_puzzle_cover_image(PuzzleSelectCoverImageRecordInput { - session_id, + session_id: compiled_session.session_id.clone(), owner_user_id, level_id: Some(target_level.level_id), candidate_id: selected_candidate_id, diff --git a/server-rs/crates/api-server/src/puzzle/handlers.rs b/server-rs/crates/api-server/src/puzzle/handlers.rs index afd6f3cf..873495f7 100644 --- a/server-rs/crates/api-server/src/puzzle/handlers.rs +++ b/server-rs/crates/api-server/src/puzzle/handlers.rs @@ -623,7 +623,7 @@ pub async fn execute_puzzle_agent_action( session_id, owner_user_id, error_message, - failed_at_micros: now, + failed_at_micros: current_utc_micros(), }) .await; if let Err(error) = result { @@ -668,27 +668,128 @@ pub async fn execute_puzzle_agent_action( Err(response) => return Err(response), }; let session = if ai_redraw { - execute_billable_asset_operation_with_cost( - state.root_state(), - &owner_user_id, - "puzzle_initial_image", - &billing_asset_id, - PUZZLE_IMAGE_GENERATION_POINTS_COST, - async { - compile_puzzle_draft_with_initial_cover( - &state, - &request_context, + if !try_register_puzzle_background_compile_task(&compile_session_id) { + tracing::info!( + provider = PUZZLE_AGENT_API_BASE_PROVIDER, + session_id = %compile_session_id, + owner_user_id = %owner_user_id, + "拼图首图后台生成任务已存在,本次 action 直接返回生成中会话" + ); + state + .spacetime_client() + .get_puzzle_agent_session( + compile_session_id.clone(), + owner_user_id.clone(), + ) + .await + .map(mark_puzzle_initial_generation_started_snapshot) + .map_err(map_puzzle_client_error) + } else { + let compiled_session = state + .spacetime_client() + .compile_puzzle_agent_draft( compile_session_id.clone(), owner_user_id.clone(), - prompt_text, - primary_reference_image_src, - payload.image_model.as_deref(), now, ) .await - }, - ) - .await + .map_err(map_puzzle_compile_error); + match compiled_session { + Ok(compiled_session) => { + let response_session = + mark_puzzle_initial_generation_started_snapshot( + compiled_session.clone(), + ); + let background_state = state.clone(); + let background_request_context = request_context.clone(); + let background_session_id = compile_session_id.clone(); + let background_owner_user_id = owner_user_id.clone(); + let background_prompt_text = prompt_text.map(str::to_string); + let background_reference_image_src = + primary_reference_image_src.map(str::to_string); + let background_image_model = payload.image_model.clone(); + let background_billing_asset_id = + format!("{background_session_id}:compile_puzzle_draft"); + tokio::spawn(async move { + let operation_owner_user_id = + background_owner_user_id.clone(); + let background_root_state = + background_state.root_state().clone(); + let operation_state = background_state.clone(); + let result = execute_billable_asset_operation_with_cost( + &background_root_state, + &background_owner_user_id, + "puzzle_initial_image", + &background_billing_asset_id, + PUZZLE_IMAGE_GENERATION_POINTS_COST, + async move { + generate_puzzle_initial_cover_from_compiled_session( + &operation_state, + &background_request_context, + compiled_session, + operation_owner_user_id, + background_prompt_text.as_deref(), + background_reference_image_src.as_deref(), + background_image_model.as_deref(), + current_utc_micros(), + ) + .await + }, + ) + .await; + match result { + Ok(session) => { + tracing::info!( + provider = PUZZLE_AGENT_API_BASE_PROVIDER, + session_id = %session.session_id, + owner_user_id = %background_owner_user_id, + "拼图首图后台生成任务完成" + ); + } + Err(error) => { + let error_message = error.body_text(); + let failure_result = background_state + .spacetime_client() + .mark_puzzle_draft_generation_failed( + PuzzleDraftCompileFailureRecordInput { + session_id: background_session_id.clone(), + owner_user_id: background_owner_user_id + .clone(), + error_message: error_message.clone(), + failed_at_micros: current_utc_micros(), + }, + ) + .await; + if let Err(mark_error) = failure_result { + tracing::warn!( + provider = PUZZLE_AGENT_API_BASE_PROVIDER, + session_id = %background_session_id, + owner_user_id = %background_owner_user_id, + message = %mark_error, + "拼图首图后台生成失败态回写失败" + ); + } + tracing::warn!( + provider = PUZZLE_AGENT_API_BASE_PROVIDER, + session_id = %background_session_id, + owner_user_id = %background_owner_user_id, + message = %error_message, + "拼图首图后台生成任务失败" + ); + } + } + unregister_puzzle_background_compile_task( + &background_session_id, + ); + }); + Ok(response_session) + } + Err(error) => { + unregister_puzzle_background_compile_task(&compile_session_id); + Err(error) + } + } + } } else { compile_puzzle_draft_with_uploaded_cover( &state, @@ -716,7 +817,7 @@ pub async fn execute_puzzle_agent_action( "compile_puzzle_draft", "首关拼图草稿", if ai_redraw { - "已编译首关草稿、并行生成首关画面和 UI 背景并写入正式草稿。" + "已编译首关草稿,并启动首关画面和 UI 资产后台生成。" } else { "已编译首关草稿,并直接应用上传图片、生成 UI 背景为第一关图片。" }, diff --git a/server-rs/crates/api-server/src/puzzle/tests.rs b/server-rs/crates/api-server/src/puzzle/tests.rs index 86512e7d..b5b902b9 100644 --- a/server-rs/crates/api-server/src/puzzle/tests.rs +++ b/server-rs/crates/api-server/src/puzzle/tests.rs @@ -980,6 +980,41 @@ fn puzzle_work_summary_response_keeps_levels_for_shelf_cover() { ); } +#[test] +fn puzzle_compile_started_snapshot_marks_primary_level_generating() { + let mut session = PuzzleAgentSessionRecord { + session_id: "puzzle-session-1".to_string(), + seed_text: "画面描述:一只猫在雨夜灯牌下回头。".to_string(), + current_turn: 1, + progress_percent: 88, + stage: "draft_ready".to_string(), + anchor_pack: test_puzzle_anchor_pack_record(), + draft: Some(test_puzzle_draft_record()), + messages: Vec::new(), + last_assistant_reply: None, + published_profile_id: None, + suggested_actions: Vec::new(), + result_preview: None, + updated_at: "2024-01-01T00:00:00Z".to_string(), + }; + { + let draft = session.draft.as_mut().expect("draft"); + draft.generation_status = "idle".to_string(); + draft.levels[0].generation_status = "idle".to_string(); + draft.levels[0].cover_image_src = None; + draft.levels[0].cover_asset_id = None; + } + + let session = mark_puzzle_initial_generation_started_snapshot(session); + let draft = session.draft.expect("draft"); + + assert_eq!(session.stage, "image_refining"); + assert_eq!(draft.generation_status, "generating"); + assert_eq!(draft.levels[0].generation_status, "generating"); + assert!(draft.cover_image_src.is_none()); + assert!(draft.levels[0].cover_image_src.is_none()); +} + #[test] fn puzzle_ui_background_prompt_keeps_generated_slots_out_of_background() { let prompt = build_puzzle_ui_background_request_prompt_for_test("雨夜猫街", "雨夜猫街主题背景"); diff --git a/server-rs/crates/api-server/src/puzzle_clear.rs b/server-rs/crates/api-server/src/puzzle_clear.rs new file mode 100644 index 00000000..1221df47 --- /dev/null +++ b/server-rs/crates/api-server/src/puzzle_clear.rs @@ -0,0 +1,2626 @@ +use axum::{ + Json, + body::{Body, to_bytes}, + extract::{Extension, Path, State, rejection::JsonRejection}, + http::{HeaderName, StatusCode, header}, + response::Response, +}; +use image::GenericImageView; +use module_assets::{ + AssetObjectAccessPolicy, build_asset_entity_binding_input, build_asset_object_upsert_input, + generate_asset_binding_id, generate_asset_object_id, +}; +use platform_oss::{LegacyAssetPrefix, OssHeadObjectRequest, OssObjectAccess}; +use serde_json::{Value, json}; +use shared_contracts::puzzle_clear::{ + PuzzleClearActionRequest, PuzzleClearActionType, PuzzleClearCardAsset, + PuzzleClearDraftResponse, PuzzleClearGenerationStatus, PuzzleClearImageAsset, + PuzzleClearNextLevelRequest, PuzzleClearPatternGroup, PuzzleClearRetryLevelRequest, + PuzzleClearRunResponse, PuzzleClearSessionResponse, PuzzleClearSessionSnapshotResponse, + PuzzleClearStartRunRequest, PuzzleClearSwapRequest, PuzzleClearTimeUpRequest, + PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse, PuzzleClearWorksResponse, + PuzzleClearWorkspaceCreateRequest, +}; +use shared_kernel::{build_prefixed_uuid_id, format_timestamp_micros}; +use spacetime_client::SpacetimeClientError; +use std::{ + collections::BTreeMap, + time::{SystemTime, UNIX_EPOCH}, +}; + +use crate::{ + api_response::json_success_body, + auth::{AuthenticatedAccessToken, RuntimePrincipal}, + generated_image_assets::{ + GeneratedImageAssetAdapter, GeneratedImageAssetDataUrl, + adapter::{GeneratedImageAssetAdapterMetadata, GeneratedImageAssetPersistInput}, + decode_generated_image_asset_data_url, normalize_generated_image_asset_mime, + }, + http_error::AppError, + openai_image_generation::{ + DownloadedOpenAiImage, build_openai_image_http_client, create_openai_image_generation, + require_openai_image_settings, + }, + request_context::RequestContext, + state::AppState, + work_play_tracking::{WorkPlayTrackingDraft, record_work_play_start_after_success}, +}; + +const PUZZLE_CLEAR_PROVIDER: &str = "puzzle-clear"; +const PUZZLE_CLEAR_CREATION_PROVIDER: &str = "puzzle-clear-creation"; +const PUZZLE_CLEAR_RUNTIME_PROVIDER: &str = "puzzle-clear-runtime"; +const PUZZLE_CLEAR_TEMPLATE_ID: &str = "puzzle-clear"; +const PUZZLE_CLEAR_TEMPLATE_NAME: &str = "拼消消"; +const PUZZLE_CLEAR_RUNTIME_RUNS_ROUTE: &str = "/api/runtime/puzzle-clear/runs"; +const PUZZLE_CLEAR_ATLAS_CELL_SIZE: u32 = 256; +const PUZZLE_CLEAR_SHEET_COLUMNS: u32 = 4; +const PUZZLE_CLEAR_SHEET_ROWS: u32 = 6; +const PUZZLE_CLEAR_SHEET_COLUMNS_USIZE: usize = 4; +const PUZZLE_CLEAR_SHEET_ROWS_USIZE: usize = 6; +const PUZZLE_CLEAR_FINAL_ATLAS_COLUMNS: u32 = 10; +const PUZZLE_CLEAR_FINAL_ATLAS_ROWS: u32 = 10; +const PUZZLE_CLEAR_SHEET_UNUSED_CELL: &str = "."; +const PUZZLE_CLEAR_SHEET_FILLER_CELL: &str = "FILL"; +const PUZZLE_CLEAR_ATLAS_GENERATION_SIZE: &str = "1024x1536"; +const PUZZLE_CLEAR_BOARD_BACKGROUND_GENERATION_SIZE: &str = "1024x1024"; +const PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC: &str = "/creation-type-references/puzzle.webp"; +const PUZZLE_CLEAR_SHEET_GENERATION_MAX_ATTEMPTS: usize = 4; +const PUZZLE_CLEAR_SHEET_FOREGROUND_DIFF_THRESHOLD: i32 = 58; +const PUZZLE_CLEAR_SHEET_MIN_FOREGROUND_RATIO: f32 = 0.018; +const PUZZLE_CLEAR_SHEET_BLANK_MAX_FOREGROUND_RATIO: f32 = 0.045; +const PUZZLE_CLEAR_SHEET_EDGE_RATIO_THRESHOLD: f32 = 0.34; +const PUZZLE_CLEAR_SHEET_STRONG_EDGE_RATIO_THRESHOLD: f32 = 0.66; +const PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_DIFF_THRESHOLD: i32 = 170; +const PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_RATIO_THRESHOLD: f32 = 0.92; +const PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_SIDE_CONTRAST_THRESHOLD: f32 = 145.0; +const PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_SIDE_TEXTURE_MAX: f32 = 36.0; +const PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT: &str = "文字、Logo、水印、按钮、UI 字、网格线、编号、标签、边框、外轮廓框、白色描边、白色贴纸边、圆角框、阴影框、分隔线、裁切参考线、单格内部拼接线、内部竖切、内部横切、照片拼贴、相册拼贴、多场景拼贴、双联图、三联图、画中画、单格双图、单格多图、低清晰度、纯色背景、空白背景、白底商品图、孤立主体、单体素材、素材表、图标、贴纸、同品种重复、同一物体多角度、重复同款小图、主体跨格、主体贴边、拼贴、重影、不同图案互相穿插"; + +pub async fn create_puzzle_clear_session( + State(state): State, + Extension(request_context): Extension, + Extension(authenticated): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_CREATION_PROVIDER)?; + validate_workspace_request(&request_context, &payload)?; + + let owner_user_id = authenticated.claims().user_id().to_string(); + let session_id = build_prefixed_uuid_id(module_puzzle_clear::PUZZLE_CLEAR_SESSION_ID_PREFIX); + let now = current_utc_micros(); + let draft = build_puzzle_clear_draft(&payload); + let session = PuzzleClearSessionSnapshotResponse { + session_id, + owner_user_id, + status: PuzzleClearGenerationStatus::Draft, + draft: Some(draft), + created_at: format_timestamp_micros(now), + updated_at: format_timestamp_micros(now), + }; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearSessionResponse { + session: state + .spacetime_client() + .create_puzzle_clear_session(session) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?, + }, + )) +} + +pub async fn get_puzzle_clear_session( + State(state): State, + Path(session_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &session_id, "sessionId")?; + let session = state + .spacetime_client() + .get_puzzle_clear_session(session_id, authenticated.claims().user_id().to_string()) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearSessionResponse { session }, + )) +} + +pub async fn execute_puzzle_clear_action( + State(state): State, + Path(session_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + ensure_non_empty(&request_context, &session_id, "sessionId")?; + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_CREATION_PROVIDER)?; + let owner_user_id = authenticated.claims().user_id().to_string(); + let author_display_name = authenticated + .claims() + .display_name + .as_deref() + .unwrap_or("拼消消玩家") + .to_string(); + let mut payload = payload; + if let Err(response) = maybe_prepare_puzzle_clear_assets_inner( + &state, + &request_context, + session_id.as_str(), + owner_user_id.as_str(), + &mut payload, + ) + .await + { + let (error_message, response) = extract_puzzle_clear_response_error_message(response).await; + tracing::warn!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + session_id, + error = %error_message, + "拼消消素材生成失败,准备回写 failed 状态" + ); + if let Err(writeback_error) = state + .spacetime_client() + .mark_puzzle_clear_generation_failed( + session_id.clone(), + owner_user_id.clone(), + author_display_name.clone(), + payload.clone(), + ) + .await + { + tracing::warn!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + session_id, + error = %writeback_error, + "拼消消素材生成失败状态回写失败" + ); + } + return Err(response); + } + let response = state + .spacetime_client() + .execute_puzzle_clear_action(session_id, owner_user_id, author_display_name, payload) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body(Some(&request_context), response)) +} + +pub async fn list_puzzle_clear_works( + State(state): State, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + let works = state + .spacetime_client() + .list_puzzle_clear_works(authenticated.claims().user_id().to_string()) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearWorksResponse { + items: works.into_iter().map(|work| work.summary).collect(), + }, + )) +} + +pub async fn get_puzzle_clear_work( + State(state): State, + Path(profile_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &profile_id, "profileId")?; + let item = state + .spacetime_client() + .get_puzzle_clear_work_profile(profile_id, authenticated.claims().user_id().to_string()) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearWorkDetailResponse { item }, + )) +} + +pub async fn publish_puzzle_clear_work( + State(state): State, + Path(profile_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &profile_id, "profileId")?; + let item = state + .spacetime_client() + .publish_puzzle_clear_work(profile_id, authenticated.claims().user_id().to_string()) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearWorkMutationResponse { item }, + )) +} + +pub async fn get_puzzle_clear_runtime_work( + State(state): State, + Path(profile_id): Path, + Extension(request_context): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &profile_id, "profileId")?; + let item = state + .spacetime_client() + .get_puzzle_clear_runtime_work(profile_id) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearWorkDetailResponse { item }, + )) +} + +pub async fn start_puzzle_clear_run( + State(state): State, + Extension(request_context): Extension, + Extension(principal): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_RUNTIME_PROVIDER)?; + ensure_non_empty(&request_context, &payload.profile_id, "profileId")?; + let owner_user_id = principal.subject().to_string(); + let principal_kind = principal.kind().as_str(); + let run = state + .spacetime_client() + .start_puzzle_clear_run(payload, owner_user_id) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + record_work_play_start_after_success( + &state, + &request_context, + build_puzzle_clear_work_play_tracking_draft( + &principal, + run.profile_id.clone(), + PUZZLE_CLEAR_RUNTIME_RUNS_ROUTE, + ) + .owner_user_id(run.owner_user_id.clone()) + .run_id(run.run_id.clone()) + .profile_id(run.profile_id.clone()) + .extra(json!({ + "runStatus": run.status, + "principalKind": principal_kind, + "levelIndex": run.level_index, + })), + ) + .await; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearRunResponse { run }, + )) +} + +pub async fn get_puzzle_clear_run( + State(state): State, + Path(run_id): Path, + Extension(request_context): Extension, + Extension(principal): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &run_id, "runId")?; + let run = state + .spacetime_client() + .get_puzzle_clear_run(run_id, principal.subject().to_string()) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearRunResponse { run }, + )) +} + +pub async fn swap_puzzle_clear_cards( + State(state): State, + Path(run_id): Path, + Extension(request_context): Extension, + Extension(principal): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + ensure_non_empty(&request_context, &run_id, "runId")?; + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_RUNTIME_PROVIDER)?; + let run = state + .spacetime_client() + .swap_puzzle_clear_cards(run_id, principal.subject().to_string(), payload) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearRunResponse { run }, + )) +} + +pub async fn retry_puzzle_clear_level( + State(state): State, + Path(run_id): Path, + Extension(request_context): Extension, + Extension(principal): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + ensure_non_empty(&request_context, &run_id, "runId")?; + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_RUNTIME_PROVIDER)?; + let run = state + .spacetime_client() + .retry_puzzle_clear_level(run_id, principal.subject().to_string(), payload) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearRunResponse { run }, + )) +} + +pub async fn advance_puzzle_clear_next_level( + State(state): State, + Path(run_id): Path, + Extension(request_context): Extension, + Extension(principal): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + ensure_non_empty(&request_context, &run_id, "runId")?; + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_RUNTIME_PROVIDER)?; + let run = state + .spacetime_client() + .advance_puzzle_clear_next_level(run_id, principal.subject().to_string(), payload) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearRunResponse { run }, + )) +} + +pub async fn mark_puzzle_clear_level_time_up( + State(state): State, + Path(run_id): Path, + Extension(request_context): Extension, + Extension(principal): Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + ensure_non_empty(&request_context, &run_id, "runId")?; + let Json(payload) = + puzzle_clear_json(payload, &request_context, PUZZLE_CLEAR_RUNTIME_PROVIDER)?; + let run = state + .spacetime_client() + .mark_puzzle_clear_level_time_up(run_id, principal.subject().to_string(), payload) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearRunResponse { run }, + )) +} + +pub async fn list_puzzle_clear_gallery( + State(state): State, + Extension(request_context): Extension, +) -> Result, Response> { + let items = state + .spacetime_client() + .list_puzzle_clear_gallery() + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + json!({ + "items": items, + "hasMore": false, + "nextCursor": null, + }), + )) +} + +pub async fn get_puzzle_clear_gallery_detail( + State(state): State, + Path(public_work_code): Path, + Extension(request_context): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &public_work_code, "publicWorkCode")?; + let normalized_code = normalize_public_work_code(public_work_code.as_str()); + let items = state + .spacetime_client() + .list_puzzle_clear_gallery() + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + let profile_id = items + .into_iter() + .find(|item| { + normalize_public_work_code( + build_puzzle_clear_public_work_code(&item.profile_id).as_str(), + ) == normalized_code + || normalize_public_work_code(item.profile_id.as_str()) == normalized_code + }) + .map(|item| item.profile_id) + .ok_or_else(|| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + AppError::from_status(StatusCode::NOT_FOUND).with_details(json!({ + "provider": PUZZLE_CLEAR_PROVIDER, + "message": "拼消消公开作品不存在", + })), + ) + })?; + let item = state + .spacetime_client() + .get_puzzle_clear_work_profile(profile_id, String::new()) + .await + .map_err(|error| { + puzzle_clear_error_response( + &request_context, + PUZZLE_CLEAR_RUNTIME_PROVIDER, + map_puzzle_clear_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + PuzzleClearWorkDetailResponse { item }, + )) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct PuzzleClearAtlasCardSlice { + group: PuzzleClearPatternGroup, + task_id: Option, + part_x: u32, + part_y: u32, + bytes: Vec, +} + +#[derive(Clone, Copy, Debug)] +struct PuzzleClearAtlasSheetSpec { + sheet_id: &'static str, + layout: [[&'static str; PUZZLE_CLEAR_SHEET_COLUMNS_USIZE]; PUZZLE_CLEAR_SHEET_ROWS_USIZE], + layout_prompt: &'static str, +} + +#[derive(Clone, Debug)] +struct PuzzleClearGeneratedSheet { + spec: PuzzleClearAtlasSheetSpec, + prompt: String, + task_id: String, + image: DownloadedOpenAiImage, +} + +async fn maybe_prepare_puzzle_clear_assets_inner( + state: &AppState, + request_context: &RequestContext, + session_id: &str, + owner_user_id: &str, + payload: &mut PuzzleClearActionRequest, +) -> Result<(), Response> { + if !matches!( + payload.action_type, + PuzzleClearActionType::CompileDraft | PuzzleClearActionType::RegenerateAtlas + ) { + return Ok(()); + } + if payload.atlas_asset.is_some() + && payload + .pattern_groups + .as_ref() + .is_some_and(|groups| !groups.is_empty()) + && payload + .card_assets + .as_ref() + .is_some_and(|cards| !cards.is_empty()) + { + return Ok(()); + } + + let profile_id = payload + .profile_id + .as_ref() + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .map(ToString::to_string) + .unwrap_or_else(|| { + build_prefixed_uuid_id(module_puzzle_clear::PUZZLE_CLEAR_PROFILE_ID_PREFIX) + }); + payload.profile_id = Some(profile_id.clone()); + + if payload.generate_board_background.unwrap_or(false) + && payload + .board_background_asset + .as_ref() + .is_none_or(|asset| asset.image_src.trim().is_empty()) + { + let board_background_prompt = payload + .board_background_prompt + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()); + let theme_prompt = payload.theme_prompt.as_deref().unwrap_or_default(); + let background_asset = generate_and_persist_puzzle_clear_board_background( + state, + request_context, + owner_user_id, + profile_id.as_str(), + board_background_prompt.unwrap_or(theme_prompt), + ) + .await?; + payload.board_background_asset = Some(background_asset); + } else if let Some(asset) = payload.board_background_asset.clone() { + if asset.image_src.trim_start().starts_with("data:image/") { + payload.board_background_asset = Some( + persist_puzzle_clear_data_url_asset( + state, + request_context, + owner_user_id, + profile_id.as_str(), + "board-background-upload", + asset.prompt.as_str(), + asset.image_src.as_str(), + 1024, + 1536, + ) + .await?, + ); + } + } + + let groups = planned_puzzle_clear_pattern_groups(); + let groups_by_id = groups + .iter() + .cloned() + .map(|group| (group.group_id.clone(), group)) + .collect::>(); + let sheet_specs = puzzle_clear_atlas_sheet_specs(); + let settings = require_openai_image_settings(state) + .map(|settings| { + settings.with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.clone()), + ) + }) + .map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?; + let http_client = build_openai_image_http_client(&settings).map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?; + let mut generated_sheets = Vec::with_capacity(sheet_specs.len()); + for sheet_spec in sheet_specs { + let sheet_prompt = build_puzzle_clear_atlas_prompt( + payload.theme_prompt.as_deref().unwrap_or_default(), + &sheet_spec, + ); + let mut accepted_sheet = None; + for attempt_index in 0..PUZZLE_CLEAR_SHEET_GENERATION_MAX_ATTEMPTS { + let failure_context = format!( + "拼消消素材 {} 生成失败,第 {} 次", + sheet_spec.sheet_id, + attempt_index + 1 + ); + let generated = match create_openai_image_generation( + &http_client, + &settings, + sheet_prompt.as_str(), + Some(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT), + PUZZLE_CLEAR_ATLAS_GENERATION_SIZE, + 1, + &[], + failure_context.as_str(), + ) + .await + { + Ok(generated) => generated, + Err(error) + if attempt_index + 1 < PUZZLE_CLEAR_SHEET_GENERATION_MAX_ATTEMPTS + && is_retryable_puzzle_clear_sheet_generation_error(&error) => + { + tracing::warn!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + sheet_id = sheet_spec.sheet_id, + attempt = attempt_index + 1, + generation_error = %error.body_text(), + "拼消消素材 sheet 生成遇到可重试上游错误,准备重试" + ); + continue; + } + Err(error) => { + return Err(puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + error, + )); + } + }; + let task_id = generated.task_id; + let image = generated.images.into_iter().next().ok_or_else(|| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "vector-engine", + "message": format!("拼消消素材 {} 生成成功但未返回图片。", sheet_spec.sheet_id), + })), + ) + })?; + match validate_puzzle_clear_sheet_quality(&image, &sheet_spec) { + Ok(()) => { + accepted_sheet = Some(PuzzleClearGeneratedSheet { + spec: sheet_spec, + prompt: sheet_prompt.clone(), + task_id, + image, + }); + break; + } + Err(error) if attempt_index + 1 < PUZZLE_CLEAR_SHEET_GENERATION_MAX_ATTEMPTS => { + tracing::warn!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + sheet_id = sheet_spec.sheet_id, + attempt = attempt_index + 1, + quality_error = %error.body_text(), + "拼消消素材 sheet 质量校验未通过,准备重试" + ); + } + Err(error) => { + tracing::warn!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + sheet_id = sheet_spec.sheet_id, + attempt = attempt_index + 1, + quality_error = %error.body_text(), + "拼消消素材 sheet 质量校验最终未通过" + ); + return Err(puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + error, + )); + } + } + } + let Some(accepted_sheet) = accepted_sheet else { + return Err(puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材 {} 多次生成后仍未得到可切图集。", sheet_spec.sheet_id), + })), + )); + }; + generated_sheets.push(accepted_sheet); + } + + let mut slices = Vec::new(); + for generated_sheet in &generated_sheets { + slices.extend( + slice_puzzle_clear_sheet( + &generated_sheet.image, + &generated_sheet.spec, + &groups_by_id, + generated_sheet.task_id.as_str(), + ) + .map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?, + ); + } + let atlas_image = + compose_puzzle_clear_final_atlas(&slices, &groups_by_id).map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?; + let atlas_prompt = generated_sheets + .iter() + .map(|sheet| format!("{}:\n{}", sheet.spec.sheet_id, sheet.prompt)) + .collect::>() + .join("\n\n---\n\n"); + let atlas_task_ids = generated_sheets + .iter() + .map(|sheet| sheet.task_id.as_str()) + .collect::>() + .join(","); + let atlas_asset = persist_puzzle_clear_generated_image_asset( + state, + owner_user_id, + profile_id.as_str(), + "atlas", + atlas_prompt.as_str(), + Some(atlas_task_ids.as_str()), + atlas_image, + PUZZLE_CLEAR_ATLAS_CELL_SIZE * PUZZLE_CLEAR_FINAL_ATLAS_COLUMNS, + PUZZLE_CLEAR_ATLAS_CELL_SIZE * PUZZLE_CLEAR_FINAL_ATLAS_ROWS, + request_context, + ) + .await?; + + let mut card_assets = Vec::with_capacity(slices.len()); + for slice in slices { + let task_id = slice.task_id.clone(); + card_assets.push( + persist_puzzle_clear_card_slice( + state, + owner_user_id, + profile_id.as_str(), + task_id.as_deref(), + slice, + request_context, + ) + .await?, + ); + } + + payload.atlas_asset = Some(atlas_asset); + payload.pattern_groups = Some(groups); + payload.card_assets = Some(card_assets); + tracing::info!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + session_id, + profile_id, + group_count = payload.pattern_groups.as_ref().map_or(0, Vec::len), + card_count = payload.card_assets.as_ref().map_or(0, Vec::len), + "拼消消素材 atlas 已生成、切片并写入资产索引" + ); + Ok(()) +} + +async fn extract_puzzle_clear_response_error_message(response: Response) -> (String, Response) { + let status = response.status(); + let (parts, body) = response.into_parts(); + let body_bytes = match to_bytes(body, 64 * 1024).await { + Ok(bytes) => bytes, + Err(_) => { + let rebuilt = Response::from_parts(parts, Body::empty()); + return (format!("拼消消素材生成失败:{status}"), rebuilt); + } + }; + let body_text = String::from_utf8_lossy(&body_bytes).trim().to_string(); + let message = if body_text.is_empty() { + format!("拼消消素材生成失败:{status}") + } else if let Ok(body_json) = serde_json::from_str::(&body_text) { + body_json + .get("error") + .and_then(|error| error.get("message")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|message| !message.is_empty()) + .unwrap_or(body_text.as_str()) + .to_string() + } else { + body_text + }; + let rebuilt = Response::from_parts(parts, Body::from(body_bytes)); + (message, rebuilt) +} + +fn planned_puzzle_clear_pattern_groups() -> Vec { + module_puzzle_clear::plan_puzzle_clear_pattern_groups(PUZZLE_CLEAR_ATLAS_CELL_SIZE) + .unwrap_or_default() + .into_iter() + .map(|group| PuzzleClearPatternGroup { + group_id: group.group_id, + shape: group.shape.as_str().to_string(), + width: group.width, + height: group.height, + atlas_x: group.atlas_x, + atlas_y: group.atlas_y, + atlas_width: group.atlas_width, + atlas_height: group.atlas_height, + }) + .collect() +} + +fn puzzle_clear_atlas_sheet_specs() -> Vec { + vec![ + PuzzleClearAtlasSheetSpec { + sheet_id: "sheet-01", + layout: [ + ["D01", "D01", "D01", "A02"], + ["D01", "D01", "D01", "A02"], + ["D02", "D02", "C01", "C01"], + ["D02", "D02", "C01", "C01"], + ["D02", "D02", "C02", "C02"], + ["A01", "A01", "C02", "C02"], + ], + layout_prompt: concat!( + "本张 sheet 的布局如下,编号只给后台理解,绝对不要画在图中:\n\n", + "第1行:D01 D01 D01 A02\n", + "第2行:D01 D01 D01 A02\n", + "第3行:D02 D02 C01 C01\n", + "第4行:D02 D02 C01 C01\n", + "第5行:D02 D02 C02 C02\n", + "第6行:A01 A01 C02 C02\n\n", + "A 表示 1x2 复合图案,C 表示 2x2 复合图案,D 表示 2x3 或 3x2 复合图案。相同编号表示同一视觉家族:请生成一组主题一致、共享同一场景锚点的小照片裁片;同组格子要像同一套连拍或同一场景的不同局部,彼此能看出是同一个故事或场景家族,但每个 256 单元仍需完整可读,不要做成彼此无关的随机独立小图。" + ), + }, + PuzzleClearAtlasSheetSpec { + sheet_id: "sheet-02", + layout: [ + ["D03", "D03", "D03", "A04"], + ["D03", "D03", "D03", "A04"], + ["C03", "C03", "C04", "C04"], + ["C03", "C03", "C04", "C04"], + ["B01", "B01", "B01", "A06"], + ["B03", "B03", "B03", "A06"], + ], + layout_prompt: concat!( + "本张 sheet 的布局如下,编号只给后台理解,绝对不要画在图中:\n\n", + "第1行:D03 D03 D03 A04\n", + "第2行:D03 D03 D03 A04\n", + "第3行:C03 C03 C04 C04\n", + "第4行:C03 C03 C04 C04\n", + "第5行:B01 B01 B01 A06\n", + "第6行:B03 B03 B03 A06\n\n", + "A 表示 1x2 复合图案,B 表示 1x3 或 3x1 复合图案,C 表示 2x2 复合图案,D 表示 2x3 或 3x2 复合图案。相同编号表示同一视觉家族:请生成一组主题一致、共享同一场景锚点的小照片裁片;同组格子要像同一套连拍或同一场景的不同局部,彼此能看出是同一个故事或场景家族,但每个 256 单元仍需完整可读,不要做成彼此无关的随机独立小图。" + ), + }, + PuzzleClearAtlasSheetSpec { + sheet_id: "sheet-03", + layout: [ + ["B02", "B04", "A03", "A03"], + ["B02", "B04", "A05", "A05"], + ["B02", "B04", "A07", "A07"], + ["B05", "B05", "B05", "A08"], + ["A09", "A09", "A10", "A08"], + ["A11", "A11", "A10", PUZZLE_CLEAR_SHEET_FILLER_CELL], + ], + layout_prompt: concat!( + "本张 sheet 的布局如下,编号只给后台理解,绝对不要画在图中:\n\n", + "第1行:B02 B04 A03 A03\n", + "第2行:B02 B04 A05 A05\n", + "第3行:B02 B04 A07 A07\n", + "第4行:B05 B05 B05 A08\n", + "第5行:A09 A09 A10 A08\n", + "第6行:A11 A11 A10 FILL\n\n", + "A 表示 1x2 复合图案,B 表示 1x3 或 3x1 复合图案。FILL 是后台会丢弃的补位格,请画成主题一致但不参与玩法的小照片裁片,不要写字或编号。相同编号表示同一视觉家族:请生成一组主题一致、共享同一场景锚点的小照片裁片;同组格子要像同一套连拍或同一场景的不同局部,彼此能看出是同一个故事或场景家族,但每个 256 单元仍需完整可读,不要做成彼此无关的随机独立小图。" + ), + }, + PuzzleClearAtlasSheetSpec { + sheet_id: "sheet-04", + layout: [ + ["A12", "A13", "A13", "A14"], + ["A12", "A15", "A15", "A14"], + ["A16", "A17", "A17", "A18"], + ["A16", "A19", "A19", "A18"], + ["A20", "A21", "A21", "A22"], + ["A20", "A23", "A23", "A22"], + ], + layout_prompt: concat!( + "本张 sheet 的布局如下,编号只给后台理解,绝对不要画在图中:\n\n", + "第1行:A12 A13 A13 A14\n", + "第2行:A12 A15 A15 A14\n", + "第3行:A16 A17 A17 A18\n", + "第4行:A16 A19 A19 A18\n", + "第5行:A20 A21 A21 A22\n", + "第6行:A20 A23 A23 A22\n\n", + "A 表示 1x2 复合图案。相同编号表示同一视觉家族:横向 1x2 和纵向 1x2 要共享同一场景锚点,用色调、道具和背景线索互相呼应;每个 256 单元仍需完整可读,但不要做成彼此无关的随机独立小图。" + ), + }, + ] +} + +fn build_puzzle_clear_atlas_prompt( + theme_prompt: &str, + sheet_spec: &PuzzleClearAtlasSheetSpec, +) -> String { + let subject = normalize_non_empty_str(theme_prompt).unwrap_or_else(|| "梦幻物件".to_string()); + format!( + concat!( + "生成一张拼消消素材工作表,主题是「{subject}」,竖版 1024x1536。\n\n", + "这张图供程序后台按 4 列 x 6 行裁切,每个裁切单元为 256x256 的正方形。4x6 网格只用于后台理解,画面中绝对不要画出网格线、切分线、边框、编号或坐标。\n\n", + "这不是单个物体素材表,而是一组照片式构图、绘本式渲染的主题微场景拼图卡。每个编号区域都要属于同一视觉家族,必须有明确背景、环境、道具、光影和构图线索,像从同一组丰富照片或插画中裁出的局部。\n\n", + "每个 256x256 单元本身就是一张完整的单场景照片裁片,单元内部只能有一个连续画面;同组之间要共享同一场景锚点、主色和道具语言。禁止在一个单元内部出现两张照片、两个不同场景、拼接线、分割线、内部竖切、内部横切、左右/上下两块不同背景。场景变化只能发生在 256 单元边界上。\n\n", + "相同编号连续占据的格子表示同一视觉家族,不是随机独立小图。请把同编号区域画成一组可辨认的兄弟卡片,至少共享一个明显场景锚点(同一张桌子、同一窗景、同一庭院、同一篮子或同一器物系统);每个格子可以展示这个家族的不同局部、视角或连贯片段,但仍需完整可读,不能在单格内部再切出第二张图或第二个场景。\n\n", + "同一张 sheet 内,不同编号必须使用不同视觉概念,并且拉开主色、场景和道具,不要把同一种主体换角度、换大小、换姿势后重复使用。比如主题是水果时,不要重复生成不同角度的葡萄、菠萝、西瓜、橙子;应扩展为果园、集市摊位、野餐布、果汁杯、厨房案板、甜品盘、篮筐、玻璃罐、窗边餐桌、花园背景等不同场景。\n\n", + "每个 256x256 单元独立查看时,都应该有可辨识的局部信息:可以包含主体局部、背景纹理、桌面、草地、天空、建筑、布料、器皿、叶片、阴影或装饰元素。不要让小卡只有一个孤立主体加纯色背景。\n\n", + "不同编号区域之间保持干净边界,主体不能越界或挤入相邻编号区域;FILL 补位格可以生成主题一致的小照片裁片,但后台会丢弃它,不要在 FILL 中写字、编号或画规则说明。\n\n", + "图案不要做成商品素材表、卡牌、贴纸、图标格子或带框小卡片。不能有外轮廓框、白色描边、圆角框、阴影框、分隔线、参考线或贴纸边。\n\n", + "画风为高清、清爽、适合休闲消除游戏的丰富主题插画;颜色鲜明,边缘干净,不能出现文字、Logo、水印、按钮、UI 或教程元素。\n\n", + "{layout_prompt}" + ), + subject = subject, + layout_prompt = sheet_spec.layout_prompt + ) +} + +fn build_puzzle_clear_board_background_prompt(theme_prompt: &str) -> String { + let subject = normalize_non_empty_str(theme_prompt).unwrap_or_else(|| "拼消消".to_string()); + format!( + concat!( + "生成拼消消中央背景底图,1:1 正方形,尺寸与中央棋盘一致,主题是「{subject}」。", + "这张图不是棋盘装饰,而是玩家逐渐消除卡片后慢慢看见的目标画面;", + "画面需要让玩家有探索、揭开底图全貌和追求目标完成的感受。", + "请以主题为核心设计精致完整的主题场景或主题主视觉,主体清晰、细节丰富、色彩和氛围强绑定主题,强表现力,必须一眼体现主题。", + "不要文字、水印、按钮、教程浮层或明显网格。" + ), + subject = subject + ) +} + +async fn generate_and_persist_puzzle_clear_board_background( + state: &AppState, + request_context: &RequestContext, + owner_user_id: &str, + profile_id: &str, + theme_prompt: &str, +) -> Result { + let prompt = build_puzzle_clear_board_background_prompt(theme_prompt); + let settings = require_openai_image_settings(state) + .map(|settings| { + settings.with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + ) + }) + .map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?; + let http_client = build_openai_image_http_client(&settings).map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?; + let generated = create_openai_image_generation( + &http_client, + &settings, + prompt.as_str(), + Some("文字、水印、按钮、教程浮层、明显网格"), + PUZZLE_CLEAR_BOARD_BACKGROUND_GENERATION_SIZE, + 1, + &[], + "拼消消场地底图生成失败", + ) + .await + .map_err(|error| { + puzzle_clear_error_response(request_context, PUZZLE_CLEAR_CREATION_PROVIDER, error) + })?; + let task_id = generated.task_id; + let image = generated.images.into_iter().next().ok_or_else(|| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "vector-engine", + "message": "拼消消场地底图生成成功但未返回图片。", + })), + ) + })?; + persist_puzzle_clear_generated_image_asset( + state, + owner_user_id, + profile_id, + "board-background", + prompt.as_str(), + Some(task_id.as_str()), + image, + 1024, + 1024, + request_context, + ) + .await +} + +async fn persist_puzzle_clear_data_url_asset( + state: &AppState, + request_context: &RequestContext, + owner_user_id: &str, + profile_id: &str, + slot: &str, + prompt: &str, + data_url: &str, + width: u32, + height: u32, +) -> Result { + let parsed = decode_generated_image_asset_data_url(data_url).map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": "generated-image-assets", + "message": format!("拼消消图片 Data URL 解析失败:{error:?}"), + })), + ) + })?; + let image = DownloadedOpenAiImage { + extension: parsed.format.extension, + mime_type: parsed.format.mime_type, + bytes: parsed.bytes, + }; + persist_puzzle_clear_generated_image_asset( + state, + owner_user_id, + profile_id, + slot, + prompt, + None, + image, + width, + height, + request_context, + ) + .await +} + +#[derive(Clone, Copy, Debug)] +struct PuzzleClearSheetCellBounds { + x0: u32, + y0: u32, + x1: u32, + y1: u32, +} + +impl PuzzleClearSheetCellBounds { + fn width(self) -> u32 { + self.x1.saturating_sub(self.x0).max(1) + } + + fn height(self) -> u32 { + self.y1.saturating_sub(self.y0).max(1) + } + + fn area(self) -> u32 { + self.width().saturating_mul(self.height()).max(1) + } +} + +#[derive(Clone, Copy, Debug)] +struct PuzzleClearSheetCellQuality { + foreground_ratio: f32, + exposed_edge_count: usize, + strongest_edge_ratio: f32, + strongest_internal_seam_ratio: f32, +} + +fn validate_puzzle_clear_sheet_quality( + image: &DownloadedOpenAiImage, + sheet_spec: &PuzzleClearAtlasSheetSpec, +) -> Result<(), AppError> { + // 中文注释:生成图进入正式切片前先做像素级门禁,避免把明显错位的 sheet 持久化成卡牌资产。 + let source = image::load_from_memory(image.bytes.as_slice()).map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材 {} 解码失败:{error}", sheet_spec.sheet_id), + })) + })?; + let source_width = source.width(); + let source_height = source.height(); + if source_width < PUZZLE_CLEAR_SHEET_COLUMNS || source_height < PUZZLE_CLEAR_SHEET_ROWS { + return Err( + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材 {} 尺寸过小,无法做切片质量校验。", sheet_spec.sheet_id), + })), + ); + } + + let mut findings = Vec::new(); + let mut advisory_findings = Vec::new(); + for row in 0..PUZZLE_CLEAR_SHEET_ROWS { + for col in 0..PUZZLE_CLEAR_SHEET_COLUMNS { + let group_id = sheet_spec.layout[row as usize][col as usize]; + let bounds = puzzle_clear_sheet_cell_bounds(row, col, source_width, source_height); + let quality = + analyze_puzzle_clear_sheet_cell_quality(&source, sheet_spec, row, col, bounds); + let cell_label = format!("第{}行第{}列", row + 1, col + 1); + if group_id == PUZZLE_CLEAR_SHEET_UNUSED_CELL { + if quality.foreground_ratio > PUZZLE_CLEAR_SHEET_BLANK_MAX_FOREGROUND_RATIO { + findings.push(format!("{cell_label} 空白格有主体")); + } + continue; + } + if group_id == PUZZLE_CLEAR_SHEET_FILLER_CELL { + continue; + } + + if quality.foreground_ratio < PUZZLE_CLEAR_SHEET_MIN_FOREGROUND_RATIO { + findings.push(format!("{cell_label} 主体过少")); + } + if quality.strongest_internal_seam_ratio + > PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_RATIO_THRESHOLD + { + findings.push(format!("{cell_label} 单格内部疑似拼接线")); + } + if quality.exposed_edge_count >= 2 + && quality.strongest_edge_ratio > PUZZLE_CLEAR_SHEET_STRONG_EDGE_RATIO_THRESHOLD + { + advisory_findings.push(format!("{cell_label} 主体贴到不同图案边界")); + } + } + } + + if !advisory_findings.is_empty() { + tracing::warn!( + provider = PUZZLE_CLEAR_CREATION_PROVIDER, + sheet_id = sheet_spec.sheet_id, + quality_warning = %advisory_findings.join(";"), + "拼消消素材 sheet 检测到边界接触,已作为提示继续切片" + ); + } + + if findings.is_empty() { + return Ok(()); + } + + Err( + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "reason": "invalid_puzzle_clear_sheet_quality", + "message": format!( + "拼消消素材 {} 不满足切片质量:{}。请重新生成图集。", + sheet_spec.sheet_id, + findings.join(";"), + ), + "findings": findings, + })), + ) +} + +fn is_retryable_puzzle_clear_sheet_generation_error(error: &AppError) -> bool { + if !matches!( + error.status_code(), + StatusCode::BAD_GATEWAY | StatusCode::GATEWAY_TIMEOUT | StatusCode::TOO_MANY_REQUESTS + ) { + return false; + } + error + .details() + .and_then(|details| details.get("retryable")) + .and_then(Value::as_bool) + .unwrap_or(false) +} + +fn analyze_puzzle_clear_sheet_cell_quality( + source: &image::DynamicImage, + sheet_spec: &PuzzleClearAtlasSheetSpec, + row: u32, + col: u32, + bounds: PuzzleClearSheetCellBounds, +) -> PuzzleClearSheetCellQuality { + let background = sample_puzzle_clear_sheet_cell_background(source, bounds); + let width = bounds.width() as usize; + let height = bounds.height() as usize; + let mut mask = vec![0u8; width.saturating_mul(height)]; + let mut foreground_pixels = 0u32; + for local_y in 0..height { + let y = bounds.y0 + local_y as u32; + for local_x in 0..width { + let x = bounds.x0 + local_x as u32; + if is_puzzle_clear_sheet_foreground_pixel(source.get_pixel(x, y).0, background) { + mask[local_y * width + local_x] = 1; + foreground_pixels = foreground_pixels.saturating_add(1); + } + } + } + + let (exposed_edge_count, strongest_edge_ratio) = + measure_puzzle_clear_sheet_exposed_edges(&mask, width, height, sheet_spec, row, col); + let strongest_internal_seam_ratio = measure_puzzle_clear_sheet_internal_seam(source, bounds); + PuzzleClearSheetCellQuality { + foreground_ratio: foreground_pixels as f32 / bounds.area() as f32, + exposed_edge_count, + strongest_edge_ratio, + strongest_internal_seam_ratio, + } +} + +fn puzzle_clear_sheet_cell_bounds( + row: u32, + col: u32, + source_width: u32, + source_height: u32, +) -> PuzzleClearSheetCellBounds { + let x0 = scale_sheet_coord(col, source_width, PUZZLE_CLEAR_SHEET_COLUMNS); + let y0 = scale_sheet_coord(row, source_height, PUZZLE_CLEAR_SHEET_ROWS); + let x1 = scale_sheet_coord(col + 1, source_width, PUZZLE_CLEAR_SHEET_COLUMNS) + .max(x0 + 1) + .min(source_width); + let y1 = scale_sheet_coord(row + 1, source_height, PUZZLE_CLEAR_SHEET_ROWS) + .max(y0 + 1) + .min(source_height); + PuzzleClearSheetCellBounds { x0, y0, x1, y1 } +} + +fn is_puzzle_clear_sheet_foreground_pixel(pixel: [u8; 4], background: [u8; 4]) -> bool { + if pixel[3] <= 24 { + return false; + } + let alpha_diff = (pixel[3] as i32 - background[3] as i32).abs(); + let color_diff = (pixel[0] as i32 - background[0] as i32).abs() + + (pixel[1] as i32 - background[1] as i32).abs() + + (pixel[2] as i32 - background[2] as i32).abs(); + alpha_diff >= 48 || color_diff >= PUZZLE_CLEAR_SHEET_FOREGROUND_DIFF_THRESHOLD +} + +fn sample_puzzle_clear_sheet_cell_background( + source: &image::DynamicImage, + bounds: PuzzleClearSheetCellBounds, +) -> [u8; 4] { + let sample_size = (bounds.width().min(bounds.height()) / 12).clamp(2, 8); + let points = [ + (bounds.x0, bounds.y0), + (bounds.x1.saturating_sub(sample_size), bounds.y0), + (bounds.x0, bounds.y1.saturating_sub(sample_size)), + ( + bounds.x1.saturating_sub(sample_size), + bounds.y1.saturating_sub(sample_size), + ), + ]; + let mut samples = Vec::new(); + for (start_x, start_y) in points { + let mut totals = [0u32; 4]; + let mut count = 0u32; + for y in start_y..start_y.saturating_add(sample_size).min(bounds.y1) { + for x in start_x..start_x.saturating_add(sample_size).min(bounds.x1) { + let pixel = source.get_pixel(x, y).0; + totals[0] = totals[0].saturating_add(pixel[0] as u32); + totals[1] = totals[1].saturating_add(pixel[1] as u32); + totals[2] = totals[2].saturating_add(pixel[2] as u32); + totals[3] = totals[3].saturating_add(pixel[3] as u32); + count = count.saturating_add(1); + } + } + if count > 0 { + samples.push([ + (totals[0] / count) as u8, + (totals[1] / count) as u8, + (totals[2] / count) as u8, + (totals[3] / count) as u8, + ]); + } + } + + samples + .into_iter() + .max_by_key(|sample| { + let max_channel = sample[0].max(sample[1]).max(sample[2]) as u16; + let min_channel = sample[0].min(sample[1]).min(sample[2]) as u16; + let luminance = sample[0] as u16 + sample[1] as u16 + sample[2] as u16; + let saturation = max_channel.saturating_sub(min_channel); + (luminance, u16::MAX.saturating_sub(saturation)) + }) + .unwrap_or([255, 255, 255, 255]) +} + +fn measure_puzzle_clear_sheet_exposed_edges( + mask: &[u8], + width: usize, + height: usize, + sheet_spec: &PuzzleClearAtlasSheetSpec, + row: u32, + col: u32, +) -> (usize, f32) { + if width == 0 || height == 0 || mask.len() < width.saturating_mul(height) { + return (0, 0.0); + } + let band = (width.min(height) / 24).clamp(6, 12); + let mut exposed_edges = 0usize; + let mut strongest_ratio = 0.0f32; + let edge_specs = [ + ((-1i32, 0i32), 0usize, 0usize, width, band), + ((1, 0), 0, height.saturating_sub(band), width, band), + ((0, -1), 0, 0, band, height), + ((0, 1), width.saturating_sub(band), 0, band, height), + ]; + + for ((row_delta, col_delta), start_x, start_y, edge_width, edge_height) in edge_specs { + if puzzle_clear_sheet_neighbor_is_same_group(sheet_spec, row, col, row_delta, col_delta) { + continue; + } + let mut foreground = 0usize; + let mut total = 0usize; + for local_y in start_y..start_y.saturating_add(edge_height).min(height) { + for local_x in start_x..start_x.saturating_add(edge_width).min(width) { + total = total.saturating_add(1); + if mask[local_y * width + local_x] != 0 { + foreground = foreground.saturating_add(1); + } + } + } + if total == 0 { + continue; + } + let ratio = foreground as f32 / total as f32; + strongest_ratio = strongest_ratio.max(ratio); + if ratio > PUZZLE_CLEAR_SHEET_EDGE_RATIO_THRESHOLD { + exposed_edges = exposed_edges.saturating_add(1); + } + } + + (exposed_edges, strongest_ratio) +} + +fn measure_puzzle_clear_sheet_internal_seam( + source: &image::DynamicImage, + bounds: PuzzleClearSheetCellBounds, +) -> f32 { + let width = bounds.width(); + let height = bounds.height(); + if width < 48 || height < 48 { + return 0.0; + } + let margin_x = (width / 8).clamp(18, 36); + let margin_y = (height / 8).clamp(18, 36); + let x_start = bounds.x0.saturating_add(margin_x).max(bounds.x0 + 1); + let x_end = bounds.x1.saturating_sub(margin_x).max(x_start + 1); + let y_start = bounds.y0.saturating_add(margin_y).max(bounds.y0 + 1); + let y_end = bounds.y1.saturating_sub(margin_y).max(y_start + 1); + let mut strongest = 0.0f32; + + for x in x_start..x_end { + let mut strong = 0u32; + let mut total = 0u32; + for y in y_start..y_end { + let left = source.get_pixel(x.saturating_sub(1), y).0; + let right = source.get_pixel(x, y).0; + if puzzle_clear_rgb_distance(left, right) + >= PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_DIFF_THRESHOLD + { + strong = strong.saturating_add(1); + } + total = total.saturating_add(1); + } + if total > 0 { + let line_ratio = strong as f32 / total as f32; + if line_ratio > PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_RATIO_THRESHOLD + && puzzle_clear_sheet_internal_seam_has_flat_split( + source, bounds, x, true, x_start, x_end, y_start, y_end, + ) + { + strongest = strongest.max(line_ratio); + } + } + } + + for y in y_start..y_end { + let mut strong = 0u32; + let mut total = 0u32; + for x in x_start..x_end { + let top = source.get_pixel(x, y.saturating_sub(1)).0; + let bottom = source.get_pixel(x, y).0; + if puzzle_clear_rgb_distance(top, bottom) + >= PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_DIFF_THRESHOLD + { + strong = strong.saturating_add(1); + } + total = total.saturating_add(1); + } + if total > 0 { + let line_ratio = strong as f32 / total as f32; + if line_ratio > PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_RATIO_THRESHOLD + && puzzle_clear_sheet_internal_seam_has_flat_split( + source, bounds, y, false, x_start, x_end, y_start, y_end, + ) + { + strongest = strongest.max(line_ratio); + } + } + } + + strongest +} + +fn puzzle_clear_sheet_internal_seam_has_flat_split( + source: &image::DynamicImage, + bounds: PuzzleClearSheetCellBounds, + split: u32, + vertical: bool, + x_start: u32, + x_end: u32, + y_start: u32, + y_end: u32, +) -> bool { + // 中文注释:富场景照片里常有窗框、桌沿、地平线等贯穿强边,只有两侧都近似人工平铺色块时才按拼贴硬失败。 + let band = (bounds.width().min(bounds.height()) / 10).clamp(14, 28); + let (first, second) = if vertical { + let left_start = split.saturating_sub(band).max(x_start); + let left_end = split.saturating_sub(2).max(left_start); + let right_start = split.saturating_add(2).min(x_end); + let right_end = split.saturating_add(band).min(x_end).max(right_start); + ( + puzzle_clear_rgb_stats_for_region(source, left_start, left_end, y_start, y_end), + puzzle_clear_rgb_stats_for_region(source, right_start, right_end, y_start, y_end), + ) + } else { + let top_start = split.saturating_sub(band).max(y_start); + let top_end = split.saturating_sub(2).max(top_start); + let bottom_start = split.saturating_add(2).min(y_end); + let bottom_end = split.saturating_add(band).min(y_end).max(bottom_start); + ( + puzzle_clear_rgb_stats_for_region(source, x_start, x_end, top_start, top_end), + puzzle_clear_rgb_stats_for_region(source, x_start, x_end, bottom_start, bottom_end), + ) + }; + + if first.count == 0 || second.count == 0 { + return false; + } + + let side_contrast = puzzle_clear_rgb_stats_distance(first, second); + let side_texture = first.texture().max(second.texture()); + side_contrast >= PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_SIDE_CONTRAST_THRESHOLD + && side_texture <= PUZZLE_CLEAR_SHEET_INTERNAL_SEAM_SIDE_TEXTURE_MAX +} + +#[derive(Clone, Copy, Debug, Default)] +struct PuzzleClearRgbStats { + count: u64, + sum: [u64; 3], + sum_square: [u64; 3], +} + +impl PuzzleClearRgbStats { + fn push(&mut self, pixel: [u8; 4]) { + self.count = self.count.saturating_add(1); + for (index, channel) in pixel.iter().take(3).enumerate() { + let value = *channel as u64; + self.sum[index] = self.sum[index].saturating_add(value); + self.sum_square[index] = self.sum_square[index].saturating_add(value * value); + } + } + + fn mean_channel(self, index: usize) -> f32 { + if self.count == 0 { + return 0.0; + } + self.sum[index] as f32 / self.count as f32 + } + + fn texture(self) -> f32 { + if self.count == 0 { + return f32::MAX; + } + let mut variance_sum = 0.0f32; + for index in 0..3 { + let mean = self.mean_channel(index); + let mean_square = self.sum_square[index] as f32 / self.count as f32; + variance_sum += (mean_square - mean * mean).max(0.0); + } + variance_sum.sqrt() + } +} + +fn puzzle_clear_rgb_stats_for_region( + source: &image::DynamicImage, + x0: u32, + x1: u32, + y0: u32, + y1: u32, +) -> PuzzleClearRgbStats { + let mut stats = PuzzleClearRgbStats::default(); + if x0 >= x1 || y0 >= y1 { + return stats; + } + for y in (y0..y1).step_by(4) { + for x in (x0..x1).step_by(4) { + stats.push(source.get_pixel(x, y).0); + } + } + stats +} + +fn puzzle_clear_rgb_stats_distance(left: PuzzleClearRgbStats, right: PuzzleClearRgbStats) -> f32 { + (0..3) + .map(|index| (left.mean_channel(index) - right.mean_channel(index)).abs()) + .sum() +} + +fn puzzle_clear_rgb_distance(left: [u8; 4], right: [u8; 4]) -> i32 { + (left[0] as i32 - right[0] as i32).abs() + + (left[1] as i32 - right[1] as i32).abs() + + (left[2] as i32 - right[2] as i32).abs() +} + +fn is_puzzle_clear_sheet_discarded_cell(group_id: &str) -> bool { + group_id == PUZZLE_CLEAR_SHEET_UNUSED_CELL || group_id == PUZZLE_CLEAR_SHEET_FILLER_CELL +} + +fn puzzle_clear_sheet_neighbor_is_same_group( + sheet_spec: &PuzzleClearAtlasSheetSpec, + row: u32, + col: u32, + row_delta: i32, + col_delta: i32, +) -> bool { + let current = sheet_spec.layout[row as usize][col as usize]; + if is_puzzle_clear_sheet_discarded_cell(current) { + return false; + } + let neighbor_row = row as i32 + row_delta; + let neighbor_col = col as i32 + col_delta; + if neighbor_row < 0 + || neighbor_col < 0 + || neighbor_row >= PUZZLE_CLEAR_SHEET_ROWS as i32 + || neighbor_col >= PUZZLE_CLEAR_SHEET_COLUMNS as i32 + { + return false; + } + sheet_spec.layout[neighbor_row as usize][neighbor_col as usize] == current +} + +fn slice_puzzle_clear_sheet( + image: &DownloadedOpenAiImage, + sheet_spec: &PuzzleClearAtlasSheetSpec, + groups_by_id: &BTreeMap, + task_id: &str, +) -> Result, AppError> { + let source = image::load_from_memory(image.bytes.as_slice()).map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材 {} 解码失败:{error}", sheet_spec.sheet_id), + })) + })?; + let source_width = source.width(); + let source_height = source.height(); + if source_width < 16 || source_height < 16 { + return Err( + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材 {} 尺寸过小,无法切割。", sheet_spec.sheet_id), + })), + ); + } + let mut slices = Vec::new(); + let mut cells_by_group: BTreeMap<&str, Vec<(u32, u32)>> = BTreeMap::new(); + for (row, cells) in sheet_spec.layout.iter().enumerate() { + for (col, group_id) in cells.iter().enumerate() { + if is_puzzle_clear_sheet_discarded_cell(group_id) { + continue; + } + cells_by_group + .entry(*group_id) + .or_default() + .push((row as u32, col as u32)); + } + } + + for (group_id, cells) in cells_by_group { + let group = groups_by_id.get(group_id).ok_or_else(|| { + AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材布局引用了未知图案组:{group_id}"), + })) + })?; + let min_row = cells.iter().map(|(row, _)| *row).min().unwrap_or(0); + let min_col = cells.iter().map(|(_, col)| *col).min().unwrap_or(0); + let expected_cell_count = (group.width * group.height) as usize; + if cells.len() != expected_cell_count { + return Err( + AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!( + "拼消消素材 {} 的布局 {} 格数不匹配,期望 {} 格,实际 {} 格。", + sheet_spec.sheet_id, + group_id, + expected_cell_count, + cells.len(), + ), + })), + ); + } + for part_y in 0..group.height { + for part_x in 0..group.width { + let expected_cell = (min_row + part_y, min_col + part_x); + if !cells.contains(&expected_cell) { + return Err(AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR) + .with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!( + "拼消消素材 {} 的布局 {} 不是完整连续矩形,缺少第 {} 行第 {} 列。", + sheet_spec.sheet_id, + group_id, + expected_cell.0 + 1, + expected_cell.1 + 1, + ), + }))); + } + } + } + for (row, col) in cells { + let part_x = col.saturating_sub(min_col); + let part_y = row.saturating_sub(min_row); + let x0 = scale_sheet_coord(col, source_width, PUZZLE_CLEAR_SHEET_COLUMNS); + let y0 = scale_sheet_coord(row, source_height, PUZZLE_CLEAR_SHEET_ROWS); + let x1 = scale_sheet_coord(col + 1, source_width, PUZZLE_CLEAR_SHEET_COLUMNS) + .max(x0 + 1) + .min(source_width); + let y1 = scale_sheet_coord(row + 1, source_height, PUZZLE_CLEAR_SHEET_ROWS) + .max(y0 + 1) + .min(source_height); + let cropped = source.crop_imm(x0, y0, x1 - x0, y1 - y0).resize_exact( + PUZZLE_CLEAR_ATLAS_CELL_SIZE, + PUZZLE_CLEAR_ATLAS_CELL_SIZE, + image::imageops::FilterType::Lanczos3, + ); + let mut cursor = std::io::Cursor::new(Vec::new()); + cropped + .write_to(&mut cursor, image::ImageFormat::Png) + .map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消素材 {} 切片失败:{error}", sheet_spec.sheet_id), + })) + })?; + slices.push(PuzzleClearAtlasCardSlice { + group: group.clone(), + task_id: Some(task_id.to_string()), + part_x, + part_y, + bytes: cursor.into_inner(), + }); + } + } + Ok(slices) +} + +fn compose_puzzle_clear_final_atlas( + slices: &[PuzzleClearAtlasCardSlice], + groups_by_id: &BTreeMap, +) -> Result { + let width = PUZZLE_CLEAR_ATLAS_CELL_SIZE * PUZZLE_CLEAR_FINAL_ATLAS_COLUMNS; + let height = PUZZLE_CLEAR_ATLAS_CELL_SIZE * PUZZLE_CLEAR_FINAL_ATLAS_ROWS; + let mut atlas = image::RgbaImage::from_pixel(width, height, image::Rgba([255, 248, 234, 255])); + for slice in slices { + let group = groups_by_id.get(&slice.group.group_id).ok_or_else(|| { + AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消最终 atlas 缺少图案组:{}", slice.group.group_id), + })) + })?; + let piece = image::load_from_memory(slice.bytes.as_slice()) + .map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消最终 atlas 切片解码失败:{error}"), + })) + })? + .to_rgba8(); + image::imageops::overlay( + &mut atlas, + &piece, + i64::from(group.atlas_x + slice.part_x * PUZZLE_CLEAR_ATLAS_CELL_SIZE), + i64::from(group.atlas_y + slice.part_y * PUZZLE_CLEAR_ATLAS_CELL_SIZE), + ); + } + let mut cursor = std::io::Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(atlas) + .write_to(&mut cursor, image::ImageFormat::Png) + .map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": PUZZLE_CLEAR_CREATION_PROVIDER, + "message": format!("拼消消最终 atlas 合成失败:{error}"), + })) + })?; + Ok(DownloadedOpenAiImage { + extension: "png".to_string(), + mime_type: "image/png".to_string(), + bytes: cursor.into_inner(), + }) +} + +fn scale_sheet_coord(value: u32, actual: u32, sheet_cells: u32) -> u32 { + ((u64::from(value) * u64::from(actual)) / u64::from(sheet_cells)) as u32 +} + +fn normalize_non_empty_str(value: &str) -> Option { + let value = value.trim(); + if value.is_empty() { + None + } else { + Some(value.to_string()) + } +} + +async fn persist_puzzle_clear_card_slice( + state: &AppState, + owner_user_id: &str, + profile_id: &str, + task_id: Option<&str>, + slice: PuzzleClearAtlasCardSlice, + request_context: &RequestContext, +) -> Result { + let card_id = format!( + "{}-part-{}-{}", + slice.group.group_id, slice.part_x, slice.part_y + ); + let prompt = format!( + "拼消消素材切片 {} {}:{}", + slice.group.group_id, slice.part_x, slice.part_y + ); + let image = DownloadedOpenAiImage { + extension: "png".to_string(), + mime_type: "image/png".to_string(), + bytes: slice.bytes, + }; + let persisted = persist_puzzle_clear_generated_image_asset( + state, + owner_user_id, + profile_id, + format!("cards/{card_id}").as_str(), + prompt.as_str(), + task_id, + image, + PUZZLE_CLEAR_ATLAS_CELL_SIZE, + PUZZLE_CLEAR_ATLAS_CELL_SIZE, + request_context, + ) + .await?; + + Ok(PuzzleClearCardAsset { + card_id, + group_id: slice.group.group_id, + shape: slice.group.shape, + orientation: if slice.group.width >= slice.group.height { + "horizontal".to_string() + } else { + "vertical".to_string() + }, + part_x: slice.part_x, + part_y: slice.part_y, + image_src: persisted.image_src, + image_object_key: persisted.image_object_key, + asset_object_id: persisted.asset_object_id, + source_atlas_cell: format!("{}:{}:{}", persisted.asset_id, slice.part_x, slice.part_y), + }) +} + +#[allow(clippy::too_many_arguments)] +async fn persist_puzzle_clear_generated_image_asset( + state: &AppState, + owner_user_id: &str, + profile_id: &str, + slot: &str, + prompt: &str, + task_id: Option<&str>, + image: DownloadedOpenAiImage, + width: u32, + height: u32, + request_context: &RequestContext, +) -> Result { + let image_format = normalize_generated_image_asset_mime(image.mime_type.as_str()); + let prepared = + GeneratedImageAssetAdapter::prepare_put_object(GeneratedImageAssetPersistInput { + prefix: LegacyAssetPrefix::PuzzleClearAssets, + path_segments: vec![profile_id.to_string(), slot.to_string()], + file_stem: "image".to_string(), + image: GeneratedImageAssetDataUrl { + format: image_format, + bytes: image.bytes, + }, + access: OssObjectAccess::Private, + metadata: GeneratedImageAssetAdapterMetadata { + asset_kind: Some(format!("puzzle-clear-{slot}")), + owner_user_id: Some(owner_user_id.to_string()), + entity_kind: Some("puzzle_clear_work".to_string()), + entity_id: Some(profile_id.to_string()), + slot: Some(slot.to_string()), + provider: Some("vector-engine".to_string()), + task_id: task_id.map(ToString::to_string), + }, + extra_metadata: BTreeMap::new(), + }) + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ + "provider": "generated-image-assets", + "message": format!("准备拼消消图片资产上传请求失败:{error:?}"), + })), + ) + })?; + let persisted_mime_type = prepared.format.mime_type.clone(); + let oss_client = state.oss_client().ok_or_else(|| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ + "provider": "aliyun-oss", + "reason": "OSS 未完成环境变量配置", + })), + ) + })?; + let http_client = reqwest::Client::new(); + let put_result = oss_client + .put_object(&http_client, prepared.request) + .await + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "aliyun-oss", + "message": error.to_string(), + })), + ) + })?; + let head = oss_client + .head_object( + &http_client, + OssHeadObjectRequest { + object_key: put_result.object_key.clone(), + }, + ) + .await + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "aliyun-oss", + "message": error.to_string(), + })), + ) + })?; + let now_micros = current_utc_micros(); + let asset_object_input = build_asset_object_upsert_input( + generate_asset_object_id(now_micros), + head.bucket, + head.object_key.clone(), + AssetObjectAccessPolicy::Private, + head.content_type.or(Some(persisted_mime_type)), + head.content_length, + head.etag, + format!("puzzle-clear-{slot}"), + task_id.map(ToString::to_string), + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + Some(profile_id.to_string()), + now_micros, + ) + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": "asset-object", + "message": error.to_string(), + })), + ) + })?; + let asset_object = state + .spacetime_client() + .confirm_asset_object(asset_object_input) + .await + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "spacetimedb", + "message": error.to_string(), + })), + ) + })?; + let binding_input = build_asset_entity_binding_input( + generate_asset_binding_id(now_micros), + asset_object.asset_object_id.clone(), + "puzzle_clear_work".to_string(), + profile_id.to_string(), + slot.to_string(), + format!("puzzle-clear-{slot}"), + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + now_micros, + ) + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": "asset-entity-binding", + "message": error.to_string(), + })), + ) + })?; + state + .spacetime_client() + .bind_asset_object_to_entity(binding_input) + .await + .map_err(|error| { + puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "spacetimedb", + "message": error.to_string(), + })), + ) + })?; + + Ok(PuzzleClearImageAsset { + asset_id: format!("{profile_id}-{}-{now_micros}", slot.replace('/', "-")), + image_src: put_result.legacy_public_path, + image_object_key: head.object_key, + asset_object_id: asset_object.asset_object_id, + generation_provider: "vector-engine".to_string(), + prompt: prompt.to_string(), + width, + height, + }) +} + +fn build_puzzle_clear_draft( + payload: &PuzzleClearWorkspaceCreateRequest, +) -> PuzzleClearDraftResponse { + PuzzleClearDraftResponse { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: None, + work_title: payload.work_title.trim().to_string(), + work_description: payload.work_description.trim().to_string(), + theme_prompt: payload.theme_prompt.trim().to_string(), + board_background_prompt: payload.board_background_prompt.trim().to_string(), + generate_board_background: payload.generate_board_background, + board_background_asset: payload.board_background_asset.clone(), + card_back_image_src: Some(PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC.to_string()), + atlas_asset: None, + pattern_groups: Vec::new(), + card_assets: Vec::new(), + generation_status: PuzzleClearGenerationStatus::Draft, + } +} + +fn validate_workspace_request( + request_context: &RequestContext, + payload: &PuzzleClearWorkspaceCreateRequest, +) -> Result<(), Response> { + if payload.template_id.trim() != PUZZLE_CLEAR_TEMPLATE_ID { + return Err(puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_CREATION_PROVIDER, + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": PUZZLE_CLEAR_PROVIDER, + "message": "templateId 必须为 puzzle-clear", + })), + )); + } + ensure_non_empty(request_context, &payload.work_title, "workTitle")?; + ensure_non_empty(request_context, &payload.theme_prompt, "themePrompt")?; + Ok(()) +} + +fn ensure_non_empty( + request_context: &RequestContext, + value: &str, + field: &str, +) -> Result<(), Response> { + if value.trim().is_empty() { + return Err(puzzle_clear_error_response( + request_context, + PUZZLE_CLEAR_PROVIDER, + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": PUZZLE_CLEAR_PROVIDER, + "field": field, + "message": format!("{field} 不能为空"), + })), + )); + } + Ok(()) +} + +fn puzzle_clear_json( + payload: Result, JsonRejection>, + request_context: &RequestContext, + provider: &str, +) -> Result, Response> { + payload.map_err(|error| { + puzzle_clear_error_response( + request_context, + provider, + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": provider, + "message": error.to_string(), + })), + ) + }) +} + +fn map_puzzle_clear_client_error(error: SpacetimeClientError) -> AppError { + let message = error.to_string(); + let status = match &error { + SpacetimeClientError::Runtime(_) => StatusCode::BAD_REQUEST, + SpacetimeClientError::Procedure(value) + if value.contains("不存在") + || value.contains("not found") + || value.contains("does not exist") => + { + StatusCode::NOT_FOUND + } + SpacetimeClientError::Procedure(value) + if value.contains("发布需要") + || value.contains("不能为空") + || value.contains("必须") + || value.contains("无权") => + { + StatusCode::BAD_REQUEST + } + _ => StatusCode::BAD_GATEWAY, + }; + + AppError::from_status(status).with_details(json!({ + "provider": "spacetimedb", + "message": message, + })) +} + +fn puzzle_clear_error_response( + request_context: &RequestContext, + provider: &str, + error: AppError, +) -> Response { + let mut response = error.into_response_with_context(Some(request_context)); + response.headers_mut().insert( + HeaderName::from_static("x-genarrative-provider"), + header::HeaderValue::from_str(provider) + .unwrap_or_else(|_| header::HeaderValue::from_static(PUZZLE_CLEAR_PROVIDER)), + ); + response +} + +fn build_puzzle_clear_work_play_tracking_draft( + principal: &RuntimePrincipal, + work_id: impl Into, + source_route: &'static str, +) -> WorkPlayTrackingDraft { + WorkPlayTrackingDraft::runtime_principal("puzzle-clear", work_id, principal, source_route) +} + +fn normalize_public_work_code(value: &str) -> String { + value + .chars() + .filter(|character| character.is_ascii_alphanumeric()) + .map(|character| character.to_ascii_uppercase()) + .collect() +} + +fn build_puzzle_clear_public_work_code(profile_id: &str) -> String { + let normalized = normalize_public_work_code(profile_id); + let fallback = if normalized.is_empty() { + "00000000".to_string() + } else { + normalized + }; + let suffix = if fallback.len() >= 8 { + fallback[fallback.len() - 8..].to_string() + } else { + format!("{fallback:0>8}") + }; + format!("PC-{suffix}") +} + +#[cfg(test)] +mod tests { + use super::{ + PUZZLE_CLEAR_ATLAS_CELL_SIZE, PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT, + PUZZLE_CLEAR_BOARD_BACKGROUND_GENERATION_SIZE, PUZZLE_CLEAR_SHEET_FILLER_CELL, + PUZZLE_CLEAR_SHEET_UNUSED_CELL, PuzzleClearAtlasSheetSpec, build_puzzle_clear_atlas_prompt, + build_puzzle_clear_board_background_prompt, build_puzzle_clear_draft, + is_puzzle_clear_sheet_discarded_cell, is_retryable_puzzle_clear_sheet_generation_error, + planned_puzzle_clear_pattern_groups, puzzle_clear_atlas_sheet_specs, + validate_puzzle_clear_sheet_quality, + }; + use crate::http_error::AppError; + use crate::openai_image_generation::DownloadedOpenAiImage; + use axum::http::StatusCode; + use image::{ImageFormat, Rgba, RgbaImage}; + use serde_json::json; + use shared_contracts::puzzle_clear::PuzzleClearWorkspaceCreateRequest; + use std::io::Cursor; + + #[test] + fn puzzle_clear_atlas_prompt_uses_sheet_cells_and_subject() { + let sheet = puzzle_clear_atlas_sheet_specs() + .into_iter() + .next() + .expect("sheet exists"); + let prompt = build_puzzle_clear_atlas_prompt("水果", &sheet); + assert!(prompt.contains("主题是「水果」")); + assert!(prompt.contains("竖版 1024x1536")); + assert!(prompt.contains("4 列 x 6 行裁切")); + assert!(prompt.contains("256x256 的正方形")); + assert!(prompt.contains("完整的单场景照片裁片")); + assert!(prompt.contains("照片式构图")); + assert!(prompt.contains("主题微场景拼图卡")); + assert!(prompt.contains("明确背景、环境、道具、光影和构图线索")); + assert!(prompt.contains("每个 256x256 单元本身就是一张完整的单场景照片裁片")); + assert!(prompt.contains("禁止在一个单元内部出现两张照片")); + assert!(prompt.contains("内部竖切")); + assert!(prompt.contains("内部横切")); + assert!(prompt.contains("场景变化只能发生在 256 单元边界上")); + assert!(prompt.contains("同一视觉家族")); + assert!(prompt.contains("同一场景锚点")); + assert!(prompt.contains("同一套连拍")); + assert!(prompt.contains("彼此无关的随机独立小图")); + assert!(prompt.contains("不能在单格内部再切出第二张图或第二个场景")); + assert!(prompt.contains("不同编号必须使用不同视觉概念")); + assert!(prompt.contains("不要把同一种主体换角度、换大小、换姿势后重复使用")); + assert!(prompt.contains("果园、集市摊位、野餐布、果汁杯、厨房案板")); + assert!(prompt.contains("可以包含主体局部、背景纹理、桌面、草地、天空")); + assert!(prompt.contains("不要让小卡只有一个孤立主体加纯色背景")); + assert!(prompt.contains("FILL 补位格可以生成主题一致的小照片裁片")); + assert!(prompt.contains("后台会丢弃它")); + assert!(prompt.contains("图案不要做成商品素材表、卡牌、贴纸、图标格子或带框小卡片")); + assert!(prompt.contains("外轮廓框")); + assert!(prompt.contains("贴纸边")); + assert!(prompt.contains("圆角框")); + assert!(prompt.contains("阴影框")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("纯色背景")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("白底商品图")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("孤立主体")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("同品种重复")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("同一物体多角度")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("单格内部拼接线")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("单格双图")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("照片拼贴")); + assert!(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT.contains("相册拼贴")); + assert!(!prompt.contains("135 幅")); + assert!(!prompt.contains("24 列 x 38 行")); + assert!(!prompt.contains("卡牌小格")); + assert!(!prompt.contains("卡牌排版图")); + assert!(!prompt.contains("贴纸表")); + } + + #[test] + fn puzzle_clear_sheet_plan_matches_reduced_asset_strategy() { + let sheets = puzzle_clear_atlas_sheet_specs(); + let groups = planned_puzzle_clear_pattern_groups(); + let occupied_sheet_cells = sheets + .iter() + .flat_map(|sheet| sheet.layout.iter().flatten()) + .filter(|group_id| **group_id != PUZZLE_CLEAR_SHEET_UNUSED_CELL) + .count(); + let playable_sheet_cells = sheets + .iter() + .flat_map(|sheet| sheet.layout.iter().flatten()) + .filter(|group_id| !is_puzzle_clear_sheet_discarded_cell(group_id)) + .count(); + let filler_sheet_cells = sheets + .iter() + .flat_map(|sheet| sheet.layout.iter().flatten()) + .filter(|group_id| **group_id == PUZZLE_CLEAR_SHEET_FILLER_CELL) + .count(); + let mut sheet_cells_by_group = std::collections::BTreeMap::<&str, u32>::new(); + for group_id in sheets + .iter() + .flat_map(|sheet| sheet.layout.iter().flatten()) + .filter(|group_id| !is_puzzle_clear_sheet_discarded_cell(group_id)) + { + *sheet_cells_by_group.entry(*group_id).or_default() += 1; + } + let group_cells = groups + .iter() + .map(|group| group.width * group.height) + .sum::(); + + assert_eq!(sheets.len(), 4); + assert_eq!(groups.len(), 35); + assert_eq!(occupied_sheet_cells, 96); + assert_eq!(playable_sheet_cells, 95); + assert_eq!(filler_sheet_cells, 1); + assert_eq!(group_cells, 95); + assert_eq!(PUZZLE_CLEAR_ATLAS_CELL_SIZE, 256); + assert_eq!(sheet_cells_by_group.len(), groups.len()); + for group in &groups { + assert_eq!( + sheet_cells_by_group.get(group.group_id.as_str()).copied(), + Some(group.width * group.height), + ); + } + } + + #[test] + fn puzzle_clear_sheet_quality_allows_edge_contact_as_advisory_warning() { + let sheet = puzzle_clear_atlas_sheet_specs() + .into_iter() + .find(|sheet| sheet.sheet_id == "sheet-04") + .expect("sheet exists"); + let mut source = RgbaImage::from_pixel(1024, 1536, Rgba([250, 249, 242, 255])); + for row in 0..6u32 { + for col in 0..4u32 { + let base_x = col * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let base_y = row * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let color = Rgba([ + 70u8.saturating_add((row * 23) as u8), + 80u8.saturating_add((col * 31) as u8), + 160, + 255, + ]); + for y in base_y + 80..base_y + 176 { + for x in base_x + 80..base_x + 176 { + source.put_pixel(x, y, color); + } + } + } + } + + for y in 0..180u32 { + for x in 0..180u32 { + source.put_pixel(x, y, Rgba([215, 48, 62, 255])); + } + } + + let mut encoded = Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(source) + .write_to(&mut encoded, ImageFormat::Png) + .expect("test image should encode"); + let image = DownloadedOpenAiImage { + extension: "png".to_string(), + mime_type: "image/png".to_string(), + bytes: encoded.into_inner(), + }; + + validate_puzzle_clear_sheet_quality(&image, &sheet) + .expect("edge contact is advisory because generated sheets often touch borders"); + } + + fn build_test_puzzle_clear_sheet_image_with_cell_pollution( + row: u32, + col: u32, + ) -> DownloadedOpenAiImage { + let mut source = RgbaImage::from_pixel(1024, 1536, Rgba([250, 249, 242, 255])); + for row in 0..6u32 { + for col in 0..4u32 { + let base_x = col * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let base_y = row * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let color = Rgba([ + 70u8.saturating_add((row * 23) as u8), + 80u8.saturating_add((col * 31) as u8), + 160, + 255, + ]); + for y in base_y + 80..base_y + 176 { + for x in base_x + 80..base_x + 176 { + source.put_pixel(x, y, color); + } + } + } + } + + let cell_y0 = row * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let cell_x0 = col * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + for y in cell_y0 + 40..cell_y0 + PUZZLE_CLEAR_ATLAS_CELL_SIZE - 40 { + for x in cell_x0 + 40..cell_x0 + PUZZLE_CLEAR_ATLAS_CELL_SIZE - 40 { + source.put_pixel(x, y, Rgba([215, 48, 62, 255])); + } + } + + let mut encoded = Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(source) + .write_to(&mut encoded, ImageFormat::Png) + .expect("test image should encode"); + DownloadedOpenAiImage { + extension: "png".to_string(), + mime_type: "image/png".to_string(), + bytes: encoded.into_inner(), + } + } + + #[test] + fn puzzle_clear_sheet_quality_allows_filler_cell_pollution() { + let sheet = puzzle_clear_atlas_sheet_specs() + .into_iter() + .find(|sheet| sheet.sheet_id == "sheet-03") + .expect("sheet exists"); + + let image = build_test_puzzle_clear_sheet_image_with_cell_pollution(5, 3); + + validate_puzzle_clear_sheet_quality(&image, &sheet) + .expect("filler cell is generated only to stabilize the sheet and is discarded later"); + } + + #[test] + fn puzzle_clear_sheet_quality_rejects_blank_cell_pollution() { + let sheet = PuzzleClearAtlasSheetSpec { + sheet_id: "blank-test", + layout: [ + [PUZZLE_CLEAR_SHEET_UNUSED_CELL, "A01", "A01", "A02"], + ["A03", "A03", "A04", "A04"], + ["A05", "A05", "A06", "A06"], + ["A07", "A07", "A08", "A08"], + ["A09", "A09", "A10", "A10"], + ["A11", "A11", "A12", "A12"], + ], + layout_prompt: "test", + }; + let image = build_test_puzzle_clear_sheet_image_with_cell_pollution(0, 0); + + let error = validate_puzzle_clear_sheet_quality(&image, &sheet) + .expect_err("blank cell pollution should be rejected"); + assert!(error.body_text().contains("空白格有主体")); + } + + #[test] + fn puzzle_clear_sheet_quality_allows_textured_scene_divider() { + let sheet = puzzle_clear_atlas_sheet_specs() + .into_iter() + .find(|sheet| sheet.sheet_id == "sheet-04") + .expect("sheet exists"); + let mut source = RgbaImage::from_pixel(1024, 1536, Rgba([250, 249, 242, 255])); + for row in 0..6u32 { + for col in 0..4u32 { + let base_x = col * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let base_y = row * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let color = Rgba([ + 70u8.saturating_add((row * 23) as u8), + 80u8.saturating_add((col * 31) as u8), + 160, + 255, + ]); + for y in base_y + 80..base_y + 176 { + for x in base_x + 80..base_x + 176 { + source.put_pixel(x, y, color); + } + } + } + } + + for y in 0..PUZZLE_CLEAR_ATLAS_CELL_SIZE { + for x in 0..PUZZLE_CLEAR_ATLAS_CELL_SIZE { + let noise = ((x * 17 + y * 31) % 120) as u8; + let color = if x < PUZZLE_CLEAR_ATLAS_CELL_SIZE / 2 { + Rgba([ + 72u8.saturating_add(noise), + 88u8.saturating_add(((x * 7 + y * 11) % 96) as u8), + 112u8.saturating_add(((x * 5 + y * 13) % 72) as u8), + 255, + ]) + } else { + Rgba([ + 104u8.saturating_add(((x * 19 + y * 3) % 92) as u8), + 76u8.saturating_add(noise), + 56u8.saturating_add(((x * 11 + y * 23) % 88) as u8), + 255, + ]) + }; + source.put_pixel(x, y, color); + } + source.put_pixel(PUZZLE_CLEAR_ATLAS_CELL_SIZE / 2, y, Rgba([24, 24, 24, 255])); + } + + let mut encoded = Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(source) + .write_to(&mut encoded, ImageFormat::Png) + .expect("test image should encode"); + let image = DownloadedOpenAiImage { + extension: "png".to_string(), + mime_type: "image/png".to_string(), + bytes: encoded.into_inner(), + }; + + validate_puzzle_clear_sheet_quality(&image, &sheet) + .expect("textured photo-like scene divider should not be rejected as collage"); + } + + #[test] + fn puzzle_clear_sheet_quality_rejects_internal_photo_seam() { + let sheet = puzzle_clear_atlas_sheet_specs() + .into_iter() + .find(|sheet| sheet.sheet_id == "sheet-04") + .expect("sheet exists"); + let mut source = RgbaImage::from_pixel(1024, 1536, Rgba([250, 249, 242, 255])); + for row in 0..6u32 { + for col in 0..4u32 { + let base_x = col * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let base_y = row * PUZZLE_CLEAR_ATLAS_CELL_SIZE; + let color = Rgba([ + 70u8.saturating_add((row * 23) as u8), + 80u8.saturating_add((col * 31) as u8), + 160, + 255, + ]); + for y in base_y + 80..base_y + 176 { + for x in base_x + 80..base_x + 176 { + source.put_pixel(x, y, color); + } + } + } + } + + for y in 0..PUZZLE_CLEAR_ATLAS_CELL_SIZE { + for x in 0..PUZZLE_CLEAR_ATLAS_CELL_SIZE / 2 { + source.put_pixel(x, y, Rgba([206, 46, 62, 255])); + } + for x in PUZZLE_CLEAR_ATLAS_CELL_SIZE / 2..PUZZLE_CLEAR_ATLAS_CELL_SIZE { + source.put_pixel(x, y, Rgba([38, 112, 218, 255])); + } + } + + let mut encoded = Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(source) + .write_to(&mut encoded, ImageFormat::Png) + .expect("test image should encode"); + let image = DownloadedOpenAiImage { + extension: "png".to_string(), + mime_type: "image/png".to_string(), + bytes: encoded.into_inner(), + }; + + let error = validate_puzzle_clear_sheet_quality(&image, &sheet) + .expect_err("internal photo seam should be rejected"); + assert!(error.body_text().contains("单格内部疑似拼接线")); + } + + #[test] + fn puzzle_clear_sheet_generation_retries_only_retryable_upstream_errors() { + let retryable_error = AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "vector-engine", + "message": "上游服务请求失败", + "retryable": true, + })); + assert!(is_retryable_puzzle_clear_sheet_generation_error( + &retryable_error + )); + + let non_retryable_gateway = + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "vector-engine", + "message": "上游服务请求失败", + "retryable": false, + })); + assert!(!is_retryable_puzzle_clear_sheet_generation_error( + &non_retryable_gateway + )); + + let bad_request = AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": "vector-engine", + "message": "请求参数不合法", + "retryable": true, + })); + assert!(!is_retryable_puzzle_clear_sheet_generation_error( + &bad_request + )); + } + + #[test] + fn puzzle_clear_board_background_prompt_reveals_theme_goal() { + let prompt = build_puzzle_clear_board_background_prompt("星港花园"); + + assert!(prompt.contains("星港花园")); + assert!(prompt.contains("逐渐消除")); + assert!(prompt.contains("底图全貌")); + assert!(prompt.contains("探索")); + assert!(prompt.contains("追求目标")); + assert!(prompt.contains("尺寸与中央棋盘一致")); + assert!(prompt.contains("精致")); + assert!(prompt.contains("强表现力")); + assert_eq!(PUZZLE_CLEAR_BOARD_BACKGROUND_GENERATION_SIZE, "1024x1024"); + assert!(!prompt.contains("画面干净")); + assert!(!prompt.contains("适合作为卡牌棋盘底图")); + assert!(!prompt.contains("9:16")); + } + + #[test] + fn puzzle_clear_draft_uses_existing_card_back_placeholder() { + let draft = build_puzzle_clear_draft(&PuzzleClearWorkspaceCreateRequest { + template_id: "puzzle-clear".to_string(), + work_title: "星港拼消消".to_string(), + work_description: String::new(), + theme_prompt: "星港".to_string(), + board_background_prompt: String::new(), + generate_board_background: true, + board_background_asset: None, + }); + + assert_eq!( + draft.card_back_image_src.as_deref(), + Some("/creation-type-references/puzzle.webp"), + ); + } +} + +fn current_utc_micros() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_micros().min(i64::MAX as u128) as i64) + .unwrap_or(0) +} diff --git a/server-rs/crates/api-server/src/wooden_fish.rs b/server-rs/crates/api-server/src/wooden_fish.rs index 4a5fe0cd..1a30d2cb 100644 --- a/server-rs/crates/api-server/src/wooden_fish.rs +++ b/server-rs/crates/api-server/src/wooden_fish.rs @@ -229,6 +229,33 @@ pub async fn list_wooden_fish_works( )) } +pub async fn delete_wooden_fish_work( + State(state): State, + Path(profile_id): Path, + Extension(request_context): Extension, + Extension(authenticated): Extension, +) -> Result, Response> { + ensure_non_empty(&request_context, &profile_id, "profileId")?; + let works = state + .spacetime_client() + .delete_wooden_fish_work(profile_id, authenticated.claims().user_id().to_string()) + .await + .map_err(|error| { + wooden_fish_error_response( + &request_context, + WOODEN_FISH_CREATION_PROVIDER, + map_wooden_fish_client_error(error), + ) + })?; + + Ok(json_success_body( + Some(&request_context), + WoodenFishWorksResponse { + items: works.into_iter().map(|work| work.summary).collect(), + }, + )) +} + pub async fn get_wooden_fish_runtime_work( State(state): State, Path(profile_id): Path, @@ -1364,6 +1391,7 @@ fn current_utc_micros() -> i64 { #[cfg(test)] mod tests { use super::*; + use crate::AppConfig; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; #[test] @@ -1535,8 +1563,7 @@ mod tests { #[tokio::test] async fn wooden_fish_draft_uses_default_hit_sound_asset_and_ignores_prompt() { - let state = crate::state::AppState::new(crate::config::AppConfig::default()) - .expect("state should build"); + let state = AppState::new(AppConfig::default()).expect("state should build"); let payload = WoodenFishWorkspaceCreateRequest { template_id: WOODEN_FISH_TEMPLATE_ID.to_string(), work_title: "今日敲木鱼".to_string(), diff --git a/server-rs/crates/module-auth/src/domain.rs b/server-rs/crates/module-auth/src/domain.rs index ac63f925..19c8dae8 100644 --- a/server-rs/crates/module-auth/src/domain.rs +++ b/server-rs/crates/module-auth/src/domain.rs @@ -57,10 +57,16 @@ pub struct AuthUser { pub display_name: String, #[serde(default)] pub avatar_url: Option, + #[serde(default)] + pub phone_number: Option, pub phone_number_masked: Option, pub login_method: AuthLoginMethod, pub binding_status: AuthBindingStatus, pub wechat_bound: bool, + #[serde(default)] + pub wechat_display_name: Option, + #[serde(default)] + pub wechat_account: Option, pub token_version: u64, #[serde(default)] pub created_at: String, diff --git a/server-rs/crates/module-auth/src/lib.rs b/server-rs/crates/module-auth/src/lib.rs index 3b0e5677..fd809932 100644 --- a/server-rs/crates/module-auth/src/lib.rs +++ b/server-rs/crates/module-auth/src/lib.rs @@ -97,6 +97,33 @@ struct StoredWechatIdentity { session_key: Option, } +fn hydrate_private_auth_fields( + state: &InMemoryAuthStoreState, + stored_user: &StoredPasswordUser, +) -> StoredPasswordUser { + let mut hydrated = stored_user.clone(); + if hydrated.user.phone_number.is_none() { + hydrated.user.phone_number = hydrated.phone_number.clone(); + } + let hydrated_wechat_identity = state + .wechat_identity_by_provider_uid + .values() + .find(|identity| identity.user_id == hydrated.user.id); + if hydrated.user.wechat_display_name.is_none() { + hydrated.user.wechat_display_name = hydrated_wechat_identity + .and_then(|identity| identity.display_name.clone()) + .or_else(|| { + (hydrated.user.login_method == AuthLoginMethod::Wechat) + .then(|| hydrated.user.display_name.clone()) + }); + } + if hydrated.user.wechat_account.is_none() { + hydrated.user.wechat_account = + hydrated_wechat_identity.map(|identity| identity.provider_uid.clone()); + } + hydrated +} + #[derive(Clone, Debug)] pub struct PasswordEntryService { store: InMemoryAuthStore, @@ -1024,6 +1051,36 @@ impl InMemoryAuthStore { .map_err(RefreshSessionError::Store) } + fn resolve_phone_user_locked( + state: &mut InMemoryAuthStoreState, + phone_number: &str, + ) -> Option { + if let Some(user_id) = state.phone_to_user_id.get(phone_number).cloned() { + if let Some(stored_user) = state + .users_by_username + .values() + .find(|stored_user| stored_user.user.id == user_id) + .cloned() + { + return Some(stored_user); + } + state.phone_to_user_id.remove(phone_number); + } + + let Some(stored_user) = state + .users_by_username + .values() + .find(|stored_user| stored_user.phone_number.as_deref() == Some(phone_number)) + .cloned() + else { + return None; + }; + state + .phone_to_user_id + .insert(phone_number.to_string(), stored_user.user.id.clone()); + Some(stored_user) + } + fn find_by_user_id( &self, user_id: &str, @@ -1037,7 +1094,7 @@ impl InMemoryAuthStore { .users_by_username .values() .find(|stored_user| stored_user.user.id == user_id) - .cloned()) + .map(|stored_user| hydrate_private_auth_fields(&state, stored_user))) } fn ensure_orphan_work_owner_user( @@ -1077,10 +1134,13 @@ impl InMemoryAuthStore { username: username.clone(), display_name, avatar_url: None, + phone_number: None, phone_number_masked: None, login_method: AuthLoginMethod::Password, binding_status: AuthBindingStatus::Active, wechat_bound: false, + wechat_display_name: None, + wechat_account: None, token_version: 1, created_at, }; @@ -1111,43 +1171,31 @@ impl InMemoryAuthStore { .users_by_username .values() .find(|stored_user| stored_user.user.public_user_code == public_user_code) - .cloned()) + .map(|stored_user| hydrate_private_auth_fields(&state, stored_user))) } fn find_by_phone_number( &self, phone_number: &str, ) -> Result, PhoneAuthError> { - let state = self + let mut state = self .inner .lock() .map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?; - let Some(user_id) = state.phone_to_user_id.get(phone_number) else { - return Ok(None); - }; - Ok(state - .users_by_username - .values() - .find(|stored_user| stored_user.user.id == *user_id) - .cloned()) + Ok(Self::resolve_phone_user_locked(&mut state, phone_number) + .map(|stored_user| hydrate_private_auth_fields(&state, &stored_user))) } fn find_by_phone_number_for_password( &self, phone_number: &str, ) -> Result, PasswordEntryError> { - let state = self + let mut state = self .inner .lock() .map_err(|_| PasswordEntryError::Store("用户仓储锁已中毒".to_string()))?; - let Some(user_id) = state.phone_to_user_id.get(phone_number) else { - return Ok(None); - }; - Ok(state - .users_by_username - .values() - .find(|stored_user| stored_user.user.id == *user_id) - .cloned()) + Ok(Self::resolve_phone_user_locked(&mut state, phone_number) + .map(|stored_user| hydrate_private_auth_fields(&state, &stored_user))) } fn update_user_profile( @@ -1190,17 +1238,10 @@ impl InMemoryAuthStore { .inner .lock() .map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?; - if let Some(existing_user_id) = state.phone_to_user_id.get(&phone_number.e164).cloned() { - let existing_user_exists = state - .users_by_username - .values() - .any(|stored_user| stored_user.user.id == existing_user_id); - if existing_user_exists { - return Err(PhoneAuthError::Store( - "手机号已存在,无法重复创建账号".to_string(), - )); - } - state.phone_to_user_id.remove(&phone_number.e164); + if Self::resolve_phone_user_locked(&mut state, &phone_number.e164).is_some() { + return Err(PhoneAuthError::Store( + "手机号已存在,无法重复创建账号".to_string(), + )); } let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| { @@ -1217,10 +1258,13 @@ impl InMemoryAuthStore { username: username.clone(), display_name, avatar_url: None, + phone_number: Some(phone_number.e164.clone()), phone_number_masked: Some(phone_number.masked_national_number.clone()), login_method: AuthLoginMethod::Phone, binding_status: AuthBindingStatus::Active, wechat_bound: false, + wechat_display_name: None, + wechat_account: None, token_version: 1, created_at, }; @@ -1251,15 +1295,8 @@ impl InMemoryAuthStore { .inner .lock() .map_err(|_| PasswordEntryError::Store("用户仓储锁已中毒".to_string()))?; - if let Some(existing_user_id) = state.phone_to_user_id.get(&phone_number.e164).cloned() { - let existing_user_exists = state - .users_by_username - .values() - .any(|stored_user| stored_user.user.id == existing_user_id); - if existing_user_exists { - return Err(PasswordEntryError::InvalidCredentials); - } - state.phone_to_user_id.remove(&phone_number.e164); + if Self::resolve_phone_user_locked(&mut state, &phone_number.e164).is_some() { + return Err(PasswordEntryError::InvalidCredentials); } let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| { @@ -1276,10 +1313,13 @@ impl InMemoryAuthStore { username: username.clone(), display_name, avatar_url: None, + phone_number: Some(phone_number.e164.clone()), phone_number_masked: Some(phone_number.masked_national_number.clone()), login_method: AuthLoginMethod::Password, binding_status: AuthBindingStatus::Active, wechat_bound: false, + wechat_display_name: None, + wechat_account: None, token_version: 1, created_at, }; @@ -1325,17 +1365,23 @@ impl InMemoryAuthStore { .filter(|value| !value.is_empty()) .unwrap_or("微信旅人") .to_string(); + let wechat_display_name = normalize_optional_string(profile.display_name.clone()) + .or_else(|| Some(display_name.clone())); let username = build_wechat_username(&display_name, &profile.provider_uid); + let provider_uid = normalize_required_string(&profile.provider_uid).unwrap_or_default(); let user = AuthUser { id: user_id.clone(), public_user_code, username: username.clone(), display_name, avatar_url: avatar_url.clone(), + phone_number: None, phone_number_masked: None, login_method: AuthLoginMethod::Wechat, binding_status: AuthBindingStatus::PendingBindPhone, wechat_bound: true, + wechat_display_name, + wechat_account: Some(provider_uid.clone()), token_version: 1, created_at, }; @@ -1350,7 +1396,7 @@ impl InMemoryAuthStore { ); let identity = StoredWechatIdentity { user_id: user_id.clone(), - provider_uid: normalize_required_string(&profile.provider_uid).unwrap_or_default(), + provider_uid, provider_union_id: normalize_optional_string(profile.provider_union_id), display_name: normalize_optional_string(profile.display_name), avatar_url, @@ -1388,7 +1434,7 @@ impl InMemoryAuthStore { .values() .find(|stored_user| stored_user.user.id == *user_id) { - return Ok(Some(stored.user.clone())); + return Ok(Some(hydrate_private_auth_fields(&state, stored).user)); } let Some(identity) = state @@ -1401,7 +1447,7 @@ impl InMemoryAuthStore { .users_by_username .values() .find(|stored_user| stored_user.user.id == identity.user_id) - .map(|stored| stored.user.clone())) + .map(|stored| hydrate_private_auth_fields(&state, stored).user)) } fn get_wechat_identity_by_user_id( @@ -1490,6 +1536,10 @@ impl InMemoryAuthStore { { stored_user.user.display_name = display_name.to_string(); } + stored_user.user.wechat_account = Some(next_provider_uid.clone()); + if let Some(display_name) = next_display_name.clone() { + stored_user.user.wechat_display_name = Some(display_name); + } stored_user.user.clone() }; self.persist_wechat_state(&state)?; @@ -1714,7 +1764,9 @@ impl InMemoryAuthStore { .lock() .map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?; - let existing_phone_user_id = state.phone_to_user_id.get(&phone_number.e164).cloned(); + let existing_phone_user_id = + Self::resolve_phone_user_locked(&mut state, &phone_number.e164) + .map(|stored_user| stored_user.user.id); if let Some(target_user_id) = existing_phone_user_id && target_user_id != pending_user_id { @@ -1724,6 +1776,8 @@ impl InMemoryAuthStore { .find(|identity| identity.user_id == pending_user_id) .cloned() .ok_or(PhoneAuthError::UserStateMismatch)?; + let pending_wechat_account = pending_wechat_identity.provider_uid.clone(); + let pending_wechat_display_name = pending_wechat_identity.display_name.clone(); let pending_username = state .users_by_username @@ -1752,6 +1806,11 @@ impl InMemoryAuthStore { .find(|stored| stored.user.id == target_user_id) .ok_or(PhoneAuthError::UserNotFound)?; target_user.user.wechat_bound = true; + target_user.user.wechat_account = Some(pending_wechat_account); + target_user.user.wechat_display_name = pending_wechat_display_name; + if target_user.user.phone_number.is_none() { + target_user.user.phone_number = target_user.phone_number.clone(); + } let next_user = target_user.user.clone(); self.persist_phone_state(&state)?; @@ -1761,15 +1820,32 @@ impl InMemoryAuthStore { state .phone_to_user_id .insert(phone_number.e164.clone(), pending_user_id.to_string()); + let bound_wechat_account = state + .wechat_identity_by_provider_uid + .values() + .find(|identity| identity.user_id == pending_user_id) + .map(|identity| identity.provider_uid.clone()); + let bound_wechat_display_name = state + .wechat_identity_by_provider_uid + .values() + .find(|identity| identity.user_id == pending_user_id) + .and_then(|identity| identity.display_name.clone()); let stored_user = state .users_by_username .values_mut() .find(|stored| stored.user.id == pending_user_id) .ok_or(PhoneAuthError::UserNotFound)?; + stored_user.user.phone_number = Some(phone_number.e164.clone()); stored_user.user.phone_number_masked = Some(phone_number.masked_national_number.clone()); stored_user.user.binding_status = AuthBindingStatus::Active; stored_user.user.wechat_bound = true; + if stored_user.user.wechat_account.is_none() { + stored_user.user.wechat_account = bound_wechat_account; + } + if stored_user.user.wechat_display_name.is_none() { + stored_user.user.wechat_display_name = bound_wechat_display_name; + } stored_user.phone_number = Some(phone_number.e164); let next_user = stored_user.user.clone(); self.persist_phone_state(&state)?; @@ -2100,10 +2176,8 @@ impl InMemoryAuthStore { .inner .lock() .map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?; - let user_id = state - .phone_to_user_id - .get(phone_number) - .cloned() + let user_id = Self::resolve_phone_user_locked(&mut state, phone_number) + .map(|stored_user| stored_user.user.id) .ok_or(PhoneAuthError::UserNotFound)?; for stored_user in state.users_by_username.values_mut() { @@ -2624,6 +2698,90 @@ mod tests { assert_eq!(error, PhoneAuthError::UserNotFound); } + #[tokio::test] + async fn dev_password_registration_ignores_orphan_phone_index() { + let snapshot = PersistentAuthStoreSnapshot { + next_user_id: 7, + users_by_username: HashMap::new(), + phone_to_user_id: HashMap::from([( + "+8613800138004".to_string(), + "user_deleted".to_string(), + )]), + sessions_by_id: HashMap::new(), + session_id_by_refresh_token_hash: HashMap::new(), + wechat_identity_by_provider_uid: HashMap::new(), + user_id_by_provider_union_id: HashMap::new(), + }; + let snapshot_json = + serde_json::to_string(&snapshot).expect("snapshot json should serialize"); + let service = build_password_service( + InMemoryAuthStore::from_snapshot_json(&snapshot_json).expect("snapshot should restore"), + ); + + let created = service + .execute_with_dev_registration(PasswordEntryInput { + phone_number: "13800138004".to_string(), + password: "secret123".to_string(), + }) + .await + .expect("orphan phone index should not block dev registration"); + + assert!(created.created); + assert_eq!( + created.user.phone_number_masked.as_deref(), + Some("138****8004") + ); + } + + #[tokio::test] + async fn phone_login_ignores_orphan_phone_index_after_code_verification() { + let snapshot = PersistentAuthStoreSnapshot { + next_user_id: 8, + users_by_username: HashMap::new(), + phone_to_user_id: HashMap::from([( + "+8613800138005".to_string(), + "user_deleted".to_string(), + )]), + sessions_by_id: HashMap::new(), + session_id_by_refresh_token_hash: HashMap::new(), + wechat_identity_by_provider_uid: HashMap::new(), + user_id_by_provider_union_id: HashMap::new(), + }; + let snapshot_json = + serde_json::to_string(&snapshot).expect("snapshot json should serialize"); + let phone_service = build_phone_service( + InMemoryAuthStore::from_snapshot_json(&snapshot_json).expect("snapshot should restore"), + ); + let now = OffsetDateTime::now_utc(); + phone_service + .send_code( + SendPhoneCodeInput { + phone_number: "13800138005".to_string(), + scene: PhoneAuthScene::Login, + }, + now, + ) + .await + .expect("phone code should send"); + + let created = phone_service + .login( + PhoneLoginInput { + phone_number: "13800138005".to_string(), + verify_code: "123456".to_string(), + }, + now + Duration::seconds(1), + ) + .await + .expect("orphan phone index should not turn login into duplicate create"); + + assert!(created.created); + assert_eq!( + created.user.phone_number_masked.as_deref(), + Some("138****8005") + ); + } + #[tokio::test] async fn snapshot_json_restores_user_and_refresh_session_after_roundtrip() { let store = InMemoryAuthStore::default(); @@ -3326,6 +3484,10 @@ mod tests { AuthBindingStatus::PendingBindPhone ); assert_eq!(first_wechat.user.username, "微信旅人甲_wx-openid-first"); + assert_eq!( + first_wechat.user.wechat_display_name.as_deref(), + Some("微信旅人甲") + ); assert!(first_wechat.user.id.starts_with("user_")); assert!(!first_wechat.user.id.ends_with("00000001")); @@ -3347,6 +3509,10 @@ mod tests { assert_ne!(second_wechat.user.id, phone_user.id); assert_eq!(second_wechat.user.login_method, AuthLoginMethod::Wechat); assert_eq!(second_wechat.user.username, first_wechat.user.username); + assert_eq!( + second_wechat.user.wechat_display_name.as_deref(), + Some("微信旅人乙") + ); } #[tokio::test] @@ -3396,6 +3562,10 @@ mod tests { wechat_user.binding_status, AuthBindingStatus::PendingBindPhone ); + assert_eq!( + wechat_user.wechat_display_name.as_deref(), + Some("待绑定微信用户") + ); assert_ne!(wechat_user.id, phone_user.id); phone_service @@ -3423,6 +3593,10 @@ mod tests { assert_eq!(merged.user.id, phone_user.id); assert_eq!(merged.user.binding_status, AuthBindingStatus::Active); assert!(merged.user.wechat_bound); + assert_eq!( + merged.user.wechat_display_name.as_deref(), + Some("待绑定微信用户") + ); let reused_wechat_user = wechat_service .resolve_login(ResolveWechatLoginInput { @@ -3440,5 +3614,9 @@ mod tests { assert!(!reused_wechat_user.created); assert_eq!(reused_wechat_user.user.id, phone_user.id); assert!(reused_wechat_user.user.wechat_bound); + assert_eq!( + reused_wechat_user.user.wechat_display_name.as_deref(), + Some("已归并微信用户") + ); } } diff --git a/server-rs/crates/module-puzzle-clear/Cargo.toml b/server-rs/crates/module-puzzle-clear/Cargo.toml new file mode 100644 index 00000000..9fb82b3f --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "module-puzzle-clear" +edition.workspace = true +version.workspace = true +license.workspace = true + +[features] +default = [] +spacetime-types = ["dep:spacetimedb"] + +[dependencies] +serde = { workspace = true } +shared-kernel = { workspace = true } +spacetimedb = { workspace = true, optional = true } diff --git a/server-rs/crates/module-puzzle-clear/src/application.rs b/server-rs/crates/module-puzzle-clear/src/application.rs new file mode 100644 index 00000000..29c7e312 --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/src/application.rs @@ -0,0 +1,1837 @@ +use std::collections::{BTreeSet, HashMap, VecDeque}; + +use shared_kernel::normalize_required_string; + +use crate::{ + PUZZLE_CLEAR_LEVEL_DURATION_SECONDS, PuzzleClearBoard, PuzzleClearCard, PuzzleClearCell, + PuzzleClearDeck, PuzzleClearElimination, PuzzleClearError, PuzzleClearLevelConfig, + PuzzleClearMove, PuzzleClearOrientation, PuzzleClearPatternGroup, PuzzleClearRunSnapshot, + PuzzleClearRunStatus, PuzzleClearShapeKind, PuzzleClearShapeQuota, +}; + +pub fn puzzle_clear_level_configs() -> Vec { + vec![PuzzleClearLevelConfig { + level_index: 1, + board_size: 6, + target_clears: 35, + duration_seconds: PUZZLE_CLEAR_LEVEL_DURATION_SECONDS, + unlocked_shapes: vec![ + PuzzleClearShapeKind::OneByTwo, + PuzzleClearShapeKind::OneByThree, + PuzzleClearShapeKind::TwoByTwo, + PuzzleClearShapeKind::TwoByThree, + ], + }] +} + +pub fn puzzle_clear_shape_quotas() -> Vec { + vec![ + PuzzleClearShapeQuota { + shape: PuzzleClearShapeKind::OneByTwo, + count: 23, + }, + PuzzleClearShapeQuota { + shape: PuzzleClearShapeKind::OneByThree, + count: 5, + }, + PuzzleClearShapeQuota { + shape: PuzzleClearShapeKind::TwoByTwo, + count: 4, + }, + PuzzleClearShapeQuota { + shape: PuzzleClearShapeKind::TwoByThree, + count: 3, + }, + ] +} + +pub fn plan_puzzle_clear_pattern_groups( + cell_size: u32, +) -> Result, PuzzleClearError> { + if cell_size == 0 { + return Err(PuzzleClearError::InvalidBoard); + } + Ok(puzzle_clear_pattern_group_specs() + .into_iter() + .map(|spec| PuzzleClearPatternGroup { + group_id: spec.group_id.to_string(), + shape: spec.shape, + width: spec.width, + height: spec.height, + atlas_x: spec.atlas_col * cell_size, + atlas_y: spec.atlas_row * cell_size, + atlas_width: spec.width * cell_size, + atlas_height: spec.height * cell_size, + }) + .collect()) +} + +pub fn build_cards_from_groups( + groups: &[PuzzleClearPatternGroup], + image_prefix: &str, +) -> Vec { + let mut cards = Vec::new(); + for group in groups { + let orientation = if group.width >= group.height { + PuzzleClearOrientation::Horizontal + } else { + PuzzleClearOrientation::Vertical + }; + for y in 0..group.height { + for x in 0..group.width { + cards.push(PuzzleClearCard { + card_id: format!("{}-part-{x}-{y}", group.group_id), + group_id: group.group_id.clone(), + shape: group.shape, + orientation, + part_x: x, + part_y: y, + image_src: format!( + "{}/{}-part-{x}-{y}.png", + image_prefix.trim_end_matches('/'), + group.group_id + ), + image_object_key: format!( + "{}/{}-part-{x}-{y}.png", + image_prefix.trim_start_matches('/').trim_end_matches('/'), + group.group_id + ), + asset_object_id: format!("{}-part-{x}-{y}-object", group.group_id), + source_atlas_cell: format!("{}:{x}:{y}", group.group_id), + }); + } + } + } + cards +} + +pub fn create_puzzle_clear_board( + level: &PuzzleClearLevelConfig, + seed: &str, + cards: Vec, +) -> Result { + if level.board_size == 0 { + return Err(PuzzleClearError::InvalidLevel); + } + let total = (level.board_size * level.board_size) as usize; + if cards.len() < total { + return Err(PuzzleClearError::EmptyDeck); + } + let mut rng = DeterministicRng::new(seed, &format!("level-{}", level.level_index)); + let mut selected = cards.into_iter().take(total).collect::>(); + shuffle(&mut selected, &mut rng); + + let mut cells = Vec::with_capacity(total); + for row in 0..level.board_size { + for col in 0..level.board_size { + let index = (row * level.board_size + col) as usize; + cells.push(PuzzleClearCell { + row, + col, + card: selected.get(index).cloned(), + locked_group_id: None, + }); + } + } + let mut board = PuzzleClearBoard { + rows: level.board_size, + cols: level.board_size, + cells, + }; + ensure_board_has_playable_move(&mut board)?; + Ok(board) +} + +pub fn start_puzzle_clear_run( + run_id: String, + owner_user_id: String, + profile_id: String, + board: PuzzleClearBoard, + deck: PuzzleClearDeck, + started_at_ms: u64, +) -> Result { + let run_id = normalize_required_string(run_id).ok_or(PuzzleClearError::MissingRunId)?; + let owner_user_id = + normalize_required_string(owner_user_id).ok_or(PuzzleClearError::MissingOwnerUserId)?; + let profile_id = + normalize_required_string(profile_id).ok_or(PuzzleClearError::MissingProfileId)?; + validate_board(&board)?; + if !has_playable_move(&board) { + return Err(PuzzleClearError::NoPlayableMove); + } + + Ok(PuzzleClearRunSnapshot { + run_id, + owner_user_id, + profile_id, + status: PuzzleClearRunStatus::Playing, + level_index: 1, + clears_done: 0, + board, + deck, + started_at_ms, + level_started_at_ms: started_at_ms, + finished_at_ms: None, + }) +} + +pub fn apply_puzzle_clear_swap( + run: &PuzzleClearRunSnapshot, + player_move: PuzzleClearMove, + now_ms: u64, +) -> Result { + ensure_run_playing(run)?; + ensure_not_expired(run, now_ms)?; + + let mut next = run.clone(); + swap_cells(&mut next.board, &player_move)?; + let mut resolved_clears = resolve_eliminations_and_refill(&mut next.board, &mut next.deck)?; + next.clears_done = next.clears_done.saturating_add(resolved_clears); + if resolved_clears == 0 { + if has_empty_cell(&next.board) { + apply_gravity_and_refill(&mut next.board, &mut next.deck)?; + resolved_clears = resolve_eliminations_and_refill(&mut next.board, &mut next.deck)?; + next.clears_done = next.clears_done.saturating_add(resolved_clears); + } + } + mark_completed_local_groups(&mut next.board); + if has_remaining_cards(&next.board) { + ensure_board_has_playable_move(&mut next.board)?; + } + let level = puzzle_clear_level_configs() + .into_iter() + .find(|config| config.level_index == next.level_index) + .ok_or(PuzzleClearError::InvalidLevel)?; + if next.clears_done >= level.target_clears && !has_remaining_cards(&next.board) { + next.status = if next.level_index >= max_puzzle_clear_level_index() { + PuzzleClearRunStatus::Finished + } else { + PuzzleClearRunStatus::LevelCleared + }; + next.finished_at_ms = Some(now_ms); + } + Ok(next) +} + +pub fn fail_puzzle_clear_level_on_timeout( + run: &PuzzleClearRunSnapshot, + now_ms: u64, +) -> Result { + ensure_run_playing(run)?; + let mut next = run.clone(); + if is_level_expired(run, now_ms) { + next.status = PuzzleClearRunStatus::LevelFailed; + next.finished_at_ms = Some(now_ms); + } + Ok(next) +} + +pub fn retry_puzzle_clear_level( + run: &PuzzleClearRunSnapshot, + board: PuzzleClearBoard, + deck: PuzzleClearDeck, + restarted_at_ms: u64, +) -> Result { + validate_board(&board)?; + if !has_playable_move(&board) { + return Err(PuzzleClearError::NoPlayableMove); + } + Ok(PuzzleClearRunSnapshot { + status: PuzzleClearRunStatus::Playing, + board, + deck, + level_started_at_ms: restarted_at_ms, + finished_at_ms: None, + ..run.clone() + }) +} + +pub fn advance_puzzle_clear_level( + run: &PuzzleClearRunSnapshot, + board: PuzzleClearBoard, + deck: PuzzleClearDeck, + started_at_ms: u64, +) -> Result { + if run.status != PuzzleClearRunStatus::LevelCleared { + return Err(PuzzleClearError::RunNotPlaying); + } + validate_board(&board)?; + if !has_playable_move(&board) { + return Err(PuzzleClearError::NoPlayableMove); + } + Ok(PuzzleClearRunSnapshot { + status: PuzzleClearRunStatus::Playing, + level_index: run.level_index.saturating_add(1), + clears_done: 0, + board, + deck, + level_started_at_ms: started_at_ms, + finished_at_ms: None, + ..run.clone() + }) +} + +pub fn has_playable_move(board: &PuzzleClearBoard) -> bool { + let Ok(()) = validate_board(board) else { + return false; + }; + if !find_eliminations(board).is_empty() { + return false; + } + for row in 0..board.rows { + for col in 0..board.cols { + if card_at(board, row, col).is_none() { + continue; + }; + for next_row in 0..board.rows { + for next_col in 0..board.cols { + if row == next_row && col == next_col { + continue; + } + let mut candidate = board.clone(); + if swap_cells( + &mut candidate, + &PuzzleClearMove { + from_row: row, + from_col: col, + to_row: next_row, + to_col: next_col, + }, + ) + .is_err() + { + continue; + } + if !find_eliminations(&candidate).is_empty() { + return true; + } + } + } + } + } + false +} + +pub fn find_eliminations(board: &PuzzleClearBoard) -> Vec { + let mut by_group: HashMap> = HashMap::new(); + for cell in &board.cells { + if let Some(card) = &cell.card { + by_group.entry(card.group_id.clone()).or_default().push(( + cell.row, + cell.col, + card.clone(), + )); + } + } + + let mut eliminations = Vec::new(); + for (group_id, entries) in by_group { + let Some(first) = entries.first().map(|(_, _, card)| card.clone()) else { + continue; + }; + let (width, height) = first.shape.dimensions(first.orientation); + if entries.len() != (width * height) as usize { + continue; + } + let min_row = entries.iter().map(|(row, _, _)| *row).min().unwrap_or(0); + let min_col = entries.iter().map(|(_, col, _)| *col).min().unwrap_or(0); + let mut expected = BTreeSet::new(); + for y in 0..height { + for x in 0..width { + expected.insert((min_row + y, min_col + x, x, y)); + } + } + let actual = entries + .iter() + .map(|(row, col, card)| (*row, *col, card.part_x, card.part_y)) + .collect::>(); + if actual == expected { + eliminations.push(PuzzleClearElimination { + group_id, + positions: entries + .into_iter() + .map(|(row, col, _)| (row, col)) + .collect(), + }); + } + } + eliminations +} + +pub fn apply_gravity_and_refill( + board: &mut PuzzleClearBoard, + deck: &mut PuzzleClearDeck, +) -> Result<(), PuzzleClearError> { + validate_board(board)?; + if deck.ready_columns.len() < board.cols as usize { + return Err(PuzzleClearError::EmptyDeck); + } + for col in 0..board.cols { + let mut segment_start = 0; + while segment_start < board.rows { + while segment_start < board.rows && is_locked_cell(board, segment_start, col) { + segment_start += 1; + } + let start = segment_start; + while segment_start < board.rows && !is_locked_cell(board, segment_start, col) { + segment_start += 1; + } + if start < segment_start { + refill_unlocked_column_segment(board, deck, col, start, segment_start)?; + } + } + } + Ok(()) +} + +fn ensure_run_playing(run: &PuzzleClearRunSnapshot) -> Result<(), PuzzleClearError> { + if run.status == PuzzleClearRunStatus::Playing { + Ok(()) + } else { + Err(PuzzleClearError::RunNotPlaying) + } +} + +fn ensure_not_expired(run: &PuzzleClearRunSnapshot, now_ms: u64) -> Result<(), PuzzleClearError> { + if is_level_expired(run, now_ms) { + Err(PuzzleClearError::LevelExpired) + } else { + Ok(()) + } +} + +fn is_level_expired(run: &PuzzleClearRunSnapshot, now_ms: u64) -> bool { + now_ms.saturating_sub(run.level_started_at_ms) + > u64::from(PUZZLE_CLEAR_LEVEL_DURATION_SECONDS) * 1000 +} + +fn validate_board(board: &PuzzleClearBoard) -> Result<(), PuzzleClearError> { + if board.rows == 0 || board.cols == 0 || board.cells.len() != (board.rows * board.cols) as usize + { + return Err(PuzzleClearError::InvalidBoard); + } + Ok(()) +} + +fn has_empty_cell(board: &PuzzleClearBoard) -> bool { + board.cells.iter().any(|cell| cell.card.is_none()) +} + +fn has_remaining_cards(board: &PuzzleClearBoard) -> bool { + board.cells.iter().any(|cell| cell.card.is_some()) +} + +fn ensure_board_has_playable_move(board: &mut PuzzleClearBoard) -> Result<(), PuzzleClearError> { + if find_eliminations(board).is_empty() && has_playable_move(board) { + return Ok(()); + } + let snapshot = board.clone(); + for from_row in 0..board.rows { + for from_col in 0..board.cols { + if cell(&snapshot, from_row, from_col) + .and_then(|cell| cell.locked_group_id.as_ref()) + .is_some() + { + continue; + } + for to_row in 0..board.rows { + for to_col in 0..board.cols { + if from_row == to_row && from_col == to_col { + continue; + } + if cell(&snapshot, to_row, to_col) + .and_then(|cell| cell.locked_group_id.as_ref()) + .is_some() + { + continue; + } + let mut candidate = snapshot.clone(); + if swap_positions(&mut candidate, from_row, from_col, to_row, to_col).is_err() { + continue; + } + if find_eliminations(&candidate).is_empty() && has_playable_move(&candidate) { + *board = candidate; + return Ok(()); + } + } + } + } + } + Err(PuzzleClearError::NoPlayableMove) +} + +fn mark_completed_local_groups(board: &mut PuzzleClearBoard) { + for elimination in find_local_completed_groups(board) { + for (row, col) in elimination.positions { + if let Some(cell) = cell_mut(board, row, col) { + cell.locked_group_id = Some(elimination.group_id.clone()); + } + } + } +} + +fn find_local_completed_groups(board: &PuzzleClearBoard) -> Vec { + let mut by_group: HashMap> = HashMap::new(); + for cell in &board.cells { + if let Some(card) = &cell.card { + by_group.entry(card.group_id.clone()).or_default().push(( + cell.row, + cell.col, + card.clone(), + )); + } + } + by_group + .into_iter() + .filter_map(|(group_id, entries)| { + let Some(first) = entries.first().map(|(_, _, card)| card.clone()) else { + return None; + }; + if entries.len() < 2 || first.shape == PuzzleClearShapeKind::OneByTwo { + return None; + } + let mut ordered = entries.clone(); + ordered.sort_by_key(|(_, _, card)| (card.part_y, card.part_x)); + let adjacent = ordered.windows(2).all(|pair| { + let a = &pair[0].2; + let b = &pair[1].2; + manhattan_part_distance(a, b) == 1 + && are_neighbors(pair[0].0, pair[0].1, pair[1].0, pair[1].1) + }); + adjacent.then(|| PuzzleClearElimination { + group_id, + positions: entries + .into_iter() + .map(|(row, col, _)| (row, col)) + .collect(), + }) + }) + .collect() +} + +fn clear_locked_group(board: &mut PuzzleClearBoard, group_id: &str) { + for cell in &mut board.cells { + if cell.locked_group_id.as_deref() == Some(group_id) { + cell.locked_group_id = None; + } + } +} + +fn clear_elimination(board: &mut PuzzleClearBoard, elimination: &PuzzleClearElimination) { + for (row, col) in &elimination.positions { + if let Some(cell) = cell_mut(board, *row, *col) { + cell.card = None; + cell.locked_group_id = None; + } + } +} + +fn resolve_eliminations_and_refill( + board: &mut PuzzleClearBoard, + deck: &mut PuzzleClearDeck, +) -> Result { + let mut resolved_clears: u32 = 0; + let max_passes = board.rows.saturating_mul(board.cols); + for _ in 0..max_passes { + let eliminations = find_eliminations(board); + if eliminations.is_empty() { + break; + } + for elimination in eliminations { + clear_elimination(board, &elimination); + resolved_clears = resolved_clears.saturating_add(1); + } + apply_gravity_and_refill(board, deck)?; + } + Ok(resolved_clears) +} + +fn swap_cells( + board: &mut PuzzleClearBoard, + player_move: &PuzzleClearMove, +) -> Result<(), PuzzleClearError> { + let Some(from_cell) = cell(board, player_move.from_row, player_move.from_col) else { + return Err(PuzzleClearError::InvalidPosition); + }; + let Some(_to_cell) = cell(board, player_move.to_row, player_move.to_col) else { + return Err(PuzzleClearError::InvalidPosition); + }; + if let Some(group_id) = cell(board, player_move.from_row, player_move.from_col) + .and_then(|cell| cell.locked_group_id.clone()) + { + return move_locked_group(board, group_id.as_str(), player_move); + } + let target_locked_group = cell(board, player_move.to_row, player_move.to_col) + .and_then(|cell| cell.locked_group_id.clone()); + let source_card = from_cell.card.clone(); + if source_card.is_none() { + return Err(PuzzleClearError::MissingCard); + } + let target_card = + cell(board, player_move.to_row, player_move.to_col).and_then(|cell| cell.card.clone()); + if target_card.is_none() { + set_card(board, player_move.to_row, player_move.to_col, source_card)?; + set_card(board, player_move.from_row, player_move.from_col, None)?; + if let Some(group_id) = target_locked_group { + clear_locked_group(board, group_id.as_str()); + } + return Ok(()); + } + swap_positions( + board, + player_move.from_row, + player_move.from_col, + player_move.to_row, + player_move.to_col, + )?; + if let Some(group_id) = target_locked_group { + clear_locked_group(board, group_id.as_str()); + } + Ok(()) +} + +fn move_locked_group( + board: &mut PuzzleClearBoard, + group_id: &str, + player_move: &PuzzleClearMove, +) -> Result<(), PuzzleClearError> { + let delta_row = i64::from(player_move.to_row) - i64::from(player_move.from_row); + let delta_col = i64::from(player_move.to_col) - i64::from(player_move.from_col); + if delta_row == 0 && delta_col == 0 { + return Ok(()); + } + + let mut old_positions = board + .cells + .iter() + .filter(|cell| cell.locked_group_id.as_deref() == Some(group_id)) + .map(|cell| (cell.row, cell.col)) + .collect::>(); + if old_positions.is_empty() { + return Err(PuzzleClearError::InvalidPosition); + } + old_positions.sort_unstable(); + + let mut new_positions = Vec::with_capacity(old_positions.len()); + for (row, col) in &old_positions { + let next_row = i64::from(*row) + delta_row; + let next_col = i64::from(*col) + delta_col; + if next_row < 0 + || next_col < 0 + || next_row >= i64::from(board.rows) + || next_col >= i64::from(board.cols) + { + return Err(PuzzleClearError::InvalidPosition); + } + new_positions.push((next_row as u32, next_col as u32)); + } + + let old_set = old_positions.iter().copied().collect::>(); + let new_set = new_positions.iter().copied().collect::>(); + if old_set == new_set { + return Ok(()); + } + + let old_only = old_set.difference(&new_set).copied().collect::>(); + let new_only = new_set.difference(&old_set).copied().collect::>(); + if old_only.len() != new_only.len() { + return Err(PuzzleClearError::InvalidPosition); + } + + let snapshot = board + .cells + .iter() + .map(|cell| ((cell.row, cell.col), cell.clone())) + .collect::>(); + let mut next_by_position = snapshot.clone(); + let mut displaced_locked_groups = BTreeSet::new(); + + for (old_position, new_position) in old_positions.iter().zip(new_positions.iter()) { + let mut moving = snapshot + .get(old_position) + .cloned() + .ok_or(PuzzleClearError::InvalidPosition)?; + moving.row = new_position.0; + moving.col = new_position.1; + moving.locked_group_id = Some(group_id.to_string()); + next_by_position.insert(*new_position, moving); + } + + for (vacated_position, displaced_position) in old_only.iter().zip(new_only.iter()) { + let mut displaced = snapshot + .get(displaced_position) + .cloned() + .ok_or(PuzzleClearError::InvalidPosition)?; + if let Some(displaced_group_id) = displaced.locked_group_id.clone() { + if displaced_group_id != group_id { + displaced_locked_groups.insert(displaced_group_id); + } + } + displaced.row = vacated_position.0; + displaced.col = vacated_position.1; + next_by_position.insert(*vacated_position, displaced); + } + + for cell in &mut board.cells { + let position = (cell.row, cell.col); + *cell = next_by_position + .get(&position) + .cloned() + .ok_or(PuzzleClearError::InvalidPosition)?; + } + for displaced_group_id in displaced_locked_groups { + clear_locked_group(board, displaced_group_id.as_str()); + } + Ok(()) +} + +fn swap_positions( + board: &mut PuzzleClearBoard, + a_row: u32, + a_col: u32, + b_row: u32, + b_col: u32, +) -> Result<(), PuzzleClearError> { + let a_index = cell_index(board, a_row, a_col).ok_or(PuzzleClearError::InvalidPosition)?; + let b_index = cell_index(board, b_row, b_col).ok_or(PuzzleClearError::InvalidPosition)?; + board.cells.swap(a_index, b_index); + board.cells[a_index].row = a_row; + board.cells[a_index].col = a_col; + board.cells[b_index].row = b_row; + board.cells[b_index].col = b_col; + Ok(()) +} + +fn take_card( + board: &mut PuzzleClearBoard, + row: u32, + col: u32, +) -> Result, PuzzleClearError> { + let cell = cell_mut(board, row, col).ok_or(PuzzleClearError::InvalidPosition)?; + cell.locked_group_id = None; + Ok(cell.card.take()) +} + +fn set_card( + board: &mut PuzzleClearBoard, + row: u32, + col: u32, + card: Option, +) -> Result<(), PuzzleClearError> { + let cell = cell_mut(board, row, col).ok_or(PuzzleClearError::InvalidPosition)?; + cell.card = card; + cell.locked_group_id = None; + Ok(()) +} + +fn refill_unlocked_column_segment( + board: &mut PuzzleClearBoard, + deck: &mut PuzzleClearDeck, + col: u32, + start_row: u32, + end_row: u32, +) -> Result<(), PuzzleClearError> { + let mut existing = VecDeque::new(); + for row in (start_row..end_row).rev() { + if let Some(card) = take_card(board, row, col)? { + existing.push_back(card); + } + } + for row in (start_row..end_row).rev() { + let next_card = if let Some(card) = existing.pop_front() { + Some(card) + } else { + pop_matching_refill_card(&mut deck.ready_columns, col as usize, board) + }; + set_card(board, row, col, next_card)?; + } + Ok(()) +} + +fn pop_matching_refill_card( + ready_columns: &mut [Vec], + preferred_col: usize, + board: &PuzzleClearBoard, +) -> Option { + fn matching_refill_index( + column: &[PuzzleClearCard], + board: &PuzzleClearBoard, + ) -> Option { + column + .iter() + .position(|candidate| can_match_remaining_field_card(candidate, board)) + } + + if preferred_col >= ready_columns.len() { + return None; + } + if let Some(index) = matching_refill_index(&ready_columns[preferred_col], board) { + return Some(ready_columns[preferred_col].remove(index)); + } + + for index in 0..ready_columns.len() { + if index == preferred_col { + continue; + } + if let Some(matching_index) = matching_refill_index(&ready_columns[index], board) { + return Some(ready_columns[index].remove(matching_index)); + } + } + + if let Some(card) = ready_columns[preferred_col].pop() { + return Some(card); + } + + for index in 0..ready_columns.len() { + if index == preferred_col { + continue; + } + if let Some(card) = ready_columns[index].pop() { + return Some(card); + } + } + None +} + +fn can_match_remaining_field_card(candidate: &PuzzleClearCard, board: &PuzzleClearBoard) -> bool { + board.cells.iter().any(|cell| { + cell.card.as_ref().is_some_and(|card| { + card.group_id == candidate.group_id && manhattan_part_distance(card, candidate) == 1 + }) + }) +} + +fn is_locked_cell(board: &PuzzleClearBoard, row: u32, col: u32) -> bool { + cell(board, row, col) + .and_then(|cell| cell.locked_group_id.as_ref()) + .is_some() +} + +fn card_at(board: &PuzzleClearBoard, row: u32, col: u32) -> Option<&PuzzleClearCard> { + cell(board, row, col)?.card.as_ref() +} + +fn cell(board: &PuzzleClearBoard, row: u32, col: u32) -> Option<&PuzzleClearCell> { + cell_index(board, row, col).and_then(|index| board.cells.get(index)) +} + +fn cell_mut(board: &mut PuzzleClearBoard, row: u32, col: u32) -> Option<&mut PuzzleClearCell> { + cell_index(board, row, col).and_then(|index| board.cells.get_mut(index)) +} + +fn cell_index(board: &PuzzleClearBoard, row: u32, col: u32) -> Option { + (row < board.rows && col < board.cols).then_some((row * board.cols + col) as usize) +} + +fn are_neighbors(a_row: u32, a_col: u32, b_row: u32, b_col: u32) -> bool { + a_row.abs_diff(b_row) + a_col.abs_diff(b_col) == 1 +} + +fn manhattan_part_distance(left: &PuzzleClearCard, right: &PuzzleClearCard) -> u32 { + left.part_x.abs_diff(right.part_x) + left.part_y.abs_diff(right.part_y) +} + +fn max_puzzle_clear_level_index() -> u32 { + puzzle_clear_level_configs() + .into_iter() + .map(|config| config.level_index) + .max() + .unwrap_or(1) +} + +struct PuzzleClearPatternGroupSpec { + group_id: &'static str, + shape: PuzzleClearShapeKind, + width: u32, + height: u32, + atlas_col: u32, + atlas_row: u32, +} + +fn puzzle_clear_pattern_group_specs() -> Vec { + use PuzzleClearShapeKind::{OneByThree, OneByTwo, TwoByThree, TwoByTwo}; + + vec![ + PuzzleClearPatternGroupSpec { + group_id: "D01", + shape: TwoByThree, + width: 3, + height: 2, + atlas_col: 0, + atlas_row: 0, + }, + PuzzleClearPatternGroupSpec { + group_id: "D02", + shape: TwoByThree, + width: 2, + height: 3, + atlas_col: 3, + atlas_row: 0, + }, + PuzzleClearPatternGroupSpec { + group_id: "D03", + shape: TwoByThree, + width: 3, + height: 2, + atlas_col: 5, + atlas_row: 0, + }, + PuzzleClearPatternGroupSpec { + group_id: "C01", + shape: TwoByTwo, + width: 2, + height: 2, + atlas_col: 8, + atlas_row: 0, + }, + PuzzleClearPatternGroupSpec { + group_id: "C02", + shape: TwoByTwo, + width: 2, + height: 2, + atlas_col: 0, + atlas_row: 2, + }, + PuzzleClearPatternGroupSpec { + group_id: "B02", + shape: OneByThree, + width: 1, + height: 3, + atlas_col: 2, + atlas_row: 2, + }, + PuzzleClearPatternGroupSpec { + group_id: "C03", + shape: TwoByTwo, + width: 2, + height: 2, + atlas_col: 5, + atlas_row: 2, + }, + PuzzleClearPatternGroupSpec { + group_id: "C04", + shape: TwoByTwo, + width: 2, + height: 2, + atlas_col: 7, + atlas_row: 2, + }, + PuzzleClearPatternGroupSpec { + group_id: "B04", + shape: OneByThree, + width: 1, + height: 3, + atlas_col: 9, + atlas_row: 2, + }, + PuzzleClearPatternGroupSpec { + group_id: "A02", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 3, + atlas_row: 3, + }, + PuzzleClearPatternGroupSpec { + group_id: "A04", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 4, + atlas_row: 3, + }, + PuzzleClearPatternGroupSpec { + group_id: "A01", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 0, + atlas_row: 4, + }, + PuzzleClearPatternGroupSpec { + group_id: "B01", + shape: OneByThree, + width: 3, + height: 1, + atlas_col: 5, + atlas_row: 4, + }, + PuzzleClearPatternGroupSpec { + group_id: "A06", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 8, + atlas_row: 4, + }, + PuzzleClearPatternGroupSpec { + group_id: "B03", + shape: OneByThree, + width: 3, + height: 1, + atlas_col: 0, + atlas_row: 5, + }, + PuzzleClearPatternGroupSpec { + group_id: "B05", + shape: OneByThree, + width: 3, + height: 1, + atlas_col: 3, + atlas_row: 5, + }, + PuzzleClearPatternGroupSpec { + group_id: "A03", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 6, + atlas_row: 5, + }, + PuzzleClearPatternGroupSpec { + group_id: "A08", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 9, + atlas_row: 5, + }, + PuzzleClearPatternGroupSpec { + group_id: "A05", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 0, + atlas_row: 6, + }, + PuzzleClearPatternGroupSpec { + group_id: "A07", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 2, + atlas_row: 6, + }, + PuzzleClearPatternGroupSpec { + group_id: "A09", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 4, + atlas_row: 6, + }, + PuzzleClearPatternGroupSpec { + group_id: "A10", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 6, + atlas_row: 6, + }, + PuzzleClearPatternGroupSpec { + group_id: "A11", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 7, + atlas_row: 6, + }, + PuzzleClearPatternGroupSpec { + group_id: "A12", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 0, + atlas_row: 7, + }, + PuzzleClearPatternGroupSpec { + group_id: "A13", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 1, + atlas_row: 7, + }, + PuzzleClearPatternGroupSpec { + group_id: "A14", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 3, + atlas_row: 7, + }, + PuzzleClearPatternGroupSpec { + group_id: "A15", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 4, + atlas_row: 7, + }, + PuzzleClearPatternGroupSpec { + group_id: "A16", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 7, + atlas_row: 7, + }, + PuzzleClearPatternGroupSpec { + group_id: "A17", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 8, + atlas_row: 7, + }, + PuzzleClearPatternGroupSpec { + group_id: "A19", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 1, + atlas_row: 8, + }, + PuzzleClearPatternGroupSpec { + group_id: "A18", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 4, + atlas_row: 8, + }, + PuzzleClearPatternGroupSpec { + group_id: "A20", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 5, + atlas_row: 8, + }, + PuzzleClearPatternGroupSpec { + group_id: "A22", + shape: OneByTwo, + width: 1, + height: 2, + atlas_col: 6, + atlas_row: 8, + }, + PuzzleClearPatternGroupSpec { + group_id: "A21", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 8, + atlas_row: 8, + }, + PuzzleClearPatternGroupSpec { + group_id: "A23", + shape: OneByTwo, + width: 2, + height: 1, + atlas_col: 0, + atlas_row: 9, + }, + ] +} + +fn shuffle(items: &mut [T], rng: &mut DeterministicRng) { + for index in (1..items.len()).rev() { + let swap_index = rng.range_usize(0, index); + items.swap(index, swap_index); + } +} + +struct DeterministicRng { + state: u64, +} + +impl DeterministicRng { + fn new(seed: &str, salt: &str) -> Self { + let mut state = 0xcbf2_9ce4_8422_2325u64; + for byte in seed.bytes().chain(salt.bytes()) { + state ^= u64::from(byte); + state = state.wrapping_mul(0x1000_0000_01b3); + } + Self { state } + } + + fn next_u32(&mut self) -> u32 { + self.state = self + .state + .wrapping_mul(6_364_136_223_846_793_005) + .wrapping_add(1); + (self.state >> 32) as u32 + } + + fn range_usize(&mut self, min: usize, max: usize) -> usize { + if max <= min { + return min; + } + min + self.next_u32() as usize % (max - min + 1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fixed_level_config_uses_single_six_by_six_level() { + let configs = puzzle_clear_level_configs(); + + assert_eq!(configs.len(), 1); + assert_eq!(configs[0].board_size, 6); + assert_eq!(configs[0].target_clears, 35); + assert_eq!( + configs[0].unlocked_shapes, + vec![ + PuzzleClearShapeKind::OneByTwo, + PuzzleClearShapeKind::OneByThree, + PuzzleClearShapeKind::TwoByTwo, + PuzzleClearShapeKind::TwoByThree, + ] + ); + assert!(configs.iter().all(|config| config.duration_seconds == 600)); + } + + #[test] + fn atlas_plan_contains_exact_shape_quotas() { + let groups = plan_puzzle_clear_pattern_groups(256).expect("atlas should plan"); + let mut counts = HashMap::new(); + for group in groups { + *counts.entry(group.shape.as_str()).or_insert(0u32) += 1; + assert_eq!(group.atlas_width, group.width * 256); + assert_eq!(group.atlas_height, group.height * 256); + assert!(group.atlas_x + group.atlas_width <= 2560); + assert!(group.atlas_y + group.atlas_height <= 2560); + } + + assert_eq!(counts.get("1x2"), Some(&23)); + assert_eq!(counts.get("1x3"), Some(&5)); + assert_eq!(counts.get("2x2"), Some(&4)); + assert_eq!(counts.get("2x3"), Some(&3)); + } + + #[test] + fn atlas_plan_includes_vertical_groups_for_rotatable_shapes() { + let groups = plan_puzzle_clear_pattern_groups(256).expect("atlas should plan"); + let vertical_groups = groups + .iter() + .filter(|group| { + matches!( + group.shape, + PuzzleClearShapeKind::OneByTwo + | PuzzleClearShapeKind::OneByThree + | PuzzleClearShapeKind::TwoByThree + ) && group.height > group.width + }) + .count(); + let cards = build_cards_from_groups(&groups, "/generated-puzzle-clear"); + + assert!(vertical_groups > 0); + assert!( + cards + .iter() + .any(|card| card.orientation == PuzzleClearOrientation::Vertical) + ); + } + + #[test] + fn board_creation_guarantees_a_playable_move() { + let groups = plan_puzzle_clear_pattern_groups(64).expect("atlas should plan"); + let cards = build_cards_from_groups(&groups, "/generated-puzzle-clear"); + let board = create_puzzle_clear_board(&puzzle_clear_level_configs()[0], "seed-a", cards) + .expect("board should create"); + + assert_eq!(board.rows, 6); + assert_eq!(board.cols, 6); + assert!(find_eliminations(&board).is_empty()); + assert!(has_playable_move(&board)); + } + + #[test] + fn one_by_two_neighbors_are_not_half_locked() { + let board = board_from_cards( + 3, + vec![ + Some(card("pair", 0, 0)), + Some(card("noise-a", 0, 0)), + Some(card("noise-b", 0, 0)), + Some(card("noise-c", 0, 0)), + Some(card("pair", 1, 0)), + Some(card("noise-d", 0, 0)), + Some(card("noise-e", 0, 0)), + Some(card("noise-f", 0, 0)), + Some(card("noise-g", 0, 0)), + ], + ); + let run = start_puzzle_clear_run( + "run-one-by-two-lock".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board, + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 100, + ) + .expect("run should start"); + + let next = apply_puzzle_clear_swap( + &run, + PuzzleClearMove { + from_row: 1, + from_col: 1, + to_row: 1, + to_col: 0, + }, + 200, + ) + .expect("swap should resolve"); + + assert!( + next.board + .cells + .iter() + .all(|cell| cell.locked_group_id.is_none()) + ); + assert_eq!(next.clears_done, 0); + } + + #[test] + fn completing_two_piece_group_eliminates_and_refills() { + let board = board_from_cards( + 3, + vec![ + Some(card("other", 0, 0)), + Some(card("noise", 0, 0)), + Some(card("other", 1, 0)), + Some(card("noise-a", 0, 0)), + Some(card("play", 0, 0)), + Some(card("noise-b", 0, 0)), + Some(card("noise-c", 0, 0)), + Some(card("play", 1, 0)), + Some(card("noise-d", 0, 0)), + ], + ); + let deck = PuzzleClearDeck { + ready_columns: vec![ + vec![], + vec![card("fill-0", 0, 0)], + vec![card("fill-1", 0, 0)], + ], + }; + let run = start_puzzle_clear_run( + "run-1".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board, + deck, + 100, + ) + .expect("run should start"); + + let next = apply_puzzle_clear_swap( + &run, + PuzzleClearMove { + from_row: 2, + from_col: 1, + to_row: 1, + to_col: 2, + }, + 200, + ) + .expect("swap should resolve"); + + assert_eq!(next.clears_done, 1); + assert!(next.board.cells.iter().all(|cell| cell.card.is_some())); + assert!(has_playable_move(&next.board)); + } + + #[test] + fn reaching_target_clears_without_empty_board_keeps_playing() { + let board = board_from_cards( + 3, + vec![ + Some(card("other", 0, 0)), + Some(card("noise", 0, 0)), + Some(card("other", 1, 0)), + Some(card("noise-a", 0, 0)), + Some(card("play", 0, 0)), + Some(card("noise-b", 0, 0)), + Some(card("noise-c", 0, 0)), + Some(card("play", 1, 0)), + Some(card("keep", 0, 0)), + ], + ); + let mut run = start_puzzle_clear_run( + "run-target".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board, + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 100, + ) + .expect("run should start"); + run.clears_done = 4; + let next = apply_puzzle_clear_swap( + &run, + PuzzleClearMove { + from_row: 2, + from_col: 1, + to_row: 1, + to_col: 2, + }, + 200, + ) + .expect("swap should resolve"); + + assert_eq!(next.clears_done, 5); + assert_eq!(next.status, PuzzleClearRunStatus::Playing); + assert!(next.board.cells.iter().any(|cell| cell.card.is_some())); + } + + #[test] + fn refill_keeps_locked_partial_group_in_place() { + let mut board = board_from_cards( + 3, + vec![ + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 0, 0)), + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 1, 0)), + Some(card("a", 0, 0)), + None, + Some(card("b", 0, 0)), + Some(card("c", 0, 0)), + Some(card("d", 0, 0)), + Some(card("e", 0, 0)), + Some(card("f", 0, 0)), + ], + ); + lock_cells(&mut board, "tri", &[(0, 0), (0, 1)]); + let mut deck = PuzzleClearDeck { + ready_columns: vec![vec![card("x", 0, 0)], vec![card("y", 0, 0)], vec![]], + }; + + apply_gravity_and_refill(&mut board, &mut deck).expect("refill should keep partial group"); + + assert_eq!( + card_at(&board, 0, 0).map(|card| card.group_id.as_str()), + Some("tri") + ); + assert_eq!( + card_at(&board, 0, 1).map(|card| card.group_id.as_str()), + Some("tri") + ); + assert_eq!( + cell(&board, 0, 0).and_then(|cell| cell.locked_group_id.as_deref()), + Some("tri") + ); + assert_eq!( + cell(&board, 0, 1).and_then(|cell| cell.locked_group_id.as_deref()), + Some("tri") + ); + } + + #[test] + fn refill_compacts_existing_cards_down_before_drawing_new_cards() { + let mut board = board_from_cards( + 3, + vec![ + Some(card("top", 0, 0)), + Some(card("a", 0, 0)), + Some(card("b", 0, 0)), + None, + Some(card("c", 0, 0)), + Some(card("d", 0, 0)), + None, + Some(card("e", 0, 0)), + Some(card("f", 0, 0)), + ], + ); + let mut deck = PuzzleClearDeck { + ready_columns: vec![ + vec![card("fill-top", 0, 0), card("fill-middle", 0, 0)], + vec![], + vec![], + ], + }; + + apply_gravity_and_refill(&mut board, &mut deck) + .expect("gravity should compact before refill"); + + assert_eq!( + card_at(&board, 2, 0).map(|card| card.group_id.as_str()), + Some("top") + ); + assert_eq!( + card_at(&board, 0, 0).map(|card| card.group_id.as_str()), + Some("fill-top") + ); + assert_eq!( + card_at(&board, 1, 0).map(|card| card.group_id.as_str()), + Some("fill-middle") + ); + } + + #[test] + fn dragging_locked_partial_group_moves_as_unit() { + let mut board = board_from_cards( + 3, + vec![ + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 0, 0)), + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 1, 0)), + Some(card("a", 0, 0)), + Some(card("x", 0, 0)), + Some(card("y", 0, 0)), + Some(card("play", 0, 0)), + Some(card("c", 0, 0)), + Some(card("d", 0, 0)), + Some(card("play", 1, 0)), + ], + ); + lock_cells(&mut board, "tri", &[(0, 0), (0, 1)]); + let run = start_puzzle_clear_run( + "run-1".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board, + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 100, + ) + .expect("run should start"); + + let next = apply_puzzle_clear_swap( + &run, + PuzzleClearMove { + from_row: 0, + from_col: 0, + to_row: 1, + to_col: 0, + }, + 200, + ) + .expect("group drag should resolve"); + + assert_eq!( + card_at(&next.board, 1, 0).map(|card| card.group_id.as_str()), + Some("tri") + ); + assert_eq!( + card_at(&next.board, 1, 1).map(|card| card.group_id.as_str()), + Some("tri") + ); + assert_eq!( + cell(&next.board, 1, 0).and_then(|cell| cell.locked_group_id.as_deref()), + Some("tri") + ); + assert_eq!( + cell(&next.board, 1, 1).and_then(|cell| cell.locked_group_id.as_deref()), + Some("tri") + ); + assert_eq!( + card_at(&next.board, 0, 0).map(|card| card.group_id.as_str()), + Some("x") + ); + assert_eq!( + card_at(&next.board, 0, 1).map(|card| card.group_id.as_str()), + Some("y") + ); + } + + #[test] + fn refill_promotes_new_card_matching_remaining_field_card() { + let mut board = board_from_cards( + 3, + vec![ + Some(card("a", 0, 0)), + None, + Some(card("b", 0, 0)), + Some(card("c", 0, 0)), + Some(card("d", 0, 0)), + Some(card("e", 0, 0)), + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 0, 0)), + Some(card("f", 0, 0)), + Some(card("g", 0, 0)), + ], + ); + let mut deck = PuzzleClearDeck { + ready_columns: vec![ + vec![], + vec![ + card_shape("tri", PuzzleClearShapeKind::OneByThree, 1, 0), + card("noise", 0, 0), + ], + vec![], + ], + }; + + apply_gravity_and_refill(&mut board, &mut deck) + .expect("refill should choose a matching new card"); + + assert_eq!( + card_at(&board, 0, 1).map(|card| card.group_id.as_str()), + Some("tri") + ); + } + + #[test] + fn refill_can_borrow_from_other_ready_columns_when_preferred_column_empty() { + let mut board = board_from_cards( + 3, + vec![ + Some(card("a", 0, 0)), + None, + Some(card("b", 0, 0)), + Some(card("c", 0, 0)), + Some(card("d", 0, 0)), + Some(card("e", 0, 0)), + Some(card("f", 0, 0)), + Some(card("g", 0, 0)), + Some(card("h", 0, 0)), + ], + ); + let mut deck = PuzzleClearDeck { + ready_columns: vec![ + vec![], + vec![], + vec![card("borrow", 0, 0), card("noise", 0, 0)], + ], + }; + + apply_gravity_and_refill(&mut board, &mut deck) + .expect("refill should borrow from another ready column"); + + assert!(board.cells.iter().all(|cell| cell.card.is_some())); + } + + #[test] + fn player_move_can_drop_card_into_empty_target_cell() { + let board = board_from_cards( + 3, + vec![ + Some(card("source", 0, 0)), + Some(card("noise-a", 0, 0)), + Some(card("noise-b", 0, 0)), + Some(card("noise-c", 0, 0)), + Some(card_shape("pair", PuzzleClearShapeKind::OneByThree, 0, 0)), + Some(card_shape("pair", PuzzleClearShapeKind::OneByThree, 1, 0)), + Some(card("play", 0, 0)), + Some(card("noise-e", 0, 0)), + Some(card("play", 1, 0)), + ], + ); + let run = start_puzzle_clear_run( + "run-empty-target".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board, + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 100, + ) + .expect("run should start"); + let mut run = run.clone(); + if let Some(cell) = cell_mut(&mut run.board, 0, 1) { + cell.card = None; + } + run.deck.ready_columns[0].push(card("refill-source", 0, 0)); + + let next = apply_puzzle_clear_swap( + &run, + PuzzleClearMove { + from_row: 0, + from_col: 0, + to_row: 0, + to_col: 1, + }, + 200, + ) + .expect("empty target should be allowed"); + + assert_eq!( + card_at(&next.board, 0, 0).map(|card| card.group_id.as_str()), + Some("refill-source") + ); + assert_eq!( + card_at(&next.board, 0, 1).map(|card| card.group_id.as_str()), + Some("source") + ); + } + + #[test] + fn non_two_piece_partial_group_can_be_locked_and_downgraded_by_player_swap() { + let board = board_from_cards( + 3, + vec![ + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 0, 0)), + Some(card_shape("tri", PuzzleClearShapeKind::OneByThree, 1, 0)), + Some(card("x", 0, 0)), + Some(card("play", 0, 0)), + Some(card("z", 0, 0)), + Some(card("w", 0, 0)), + Some(card("m", 0, 0)), + Some(card("n", 0, 0)), + Some(card("play", 1, 0)), + ], + ); + let run = start_puzzle_clear_run( + "run-1".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board, + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 100, + ) + .expect("run should start"); + let locked = apply_puzzle_clear_swap( + &run, + PuzzleClearMove { + from_row: 2, + from_col: 2, + to_row: 1, + to_col: 2, + }, + 200, + ) + .expect("non-clear swap should mark partials"); + + assert_eq!( + locked.board.cells[0].locked_group_id.as_deref(), + Some("tri") + ); + assert_eq!( + locked.board.cells[1].locked_group_id.as_deref(), + Some("tri") + ); + + let downgraded = apply_puzzle_clear_swap( + &locked, + PuzzleClearMove { + from_row: 1, + from_col: 0, + to_row: 0, + to_col: 0, + }, + 250, + ) + .expect("player swap into group should downgrade lock"); + + assert!( + downgraded + .board + .cells + .iter() + .filter(|cell| cell + .card + .as_ref() + .is_some_and(|card| card.group_id == "tri")) + .all(|cell| cell.locked_group_id.is_none()) + ); + } + + #[test] + fn timeout_fails_only_current_level_and_retry_restarts_it() { + let board = board_from_cards( + 3, + vec![ + Some(card("a", 0, 0)), + Some(card("b", 0, 0)), + Some(card("b", 0, 0)), + Some(card("c", 0, 0)), + Some(card("a", 1, 0)), + Some(card("e", 0, 0)), + Some(card("f", 0, 0)), + Some(card("g", 0, 0)), + Some(card("h", 0, 0)), + ], + ); + let run = start_puzzle_clear_run( + "run-1".to_string(), + "user-1".to_string(), + "profile-1".to_string(), + board.clone(), + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 0, + ) + .expect("run should start"); + + let failed = fail_puzzle_clear_level_on_timeout(&run, 601_000).expect("timeout applies"); + assert_eq!(failed.status, PuzzleClearRunStatus::LevelFailed); + assert_eq!(failed.level_index, 1); + + let retried = retry_puzzle_clear_level( + &failed, + board, + PuzzleClearDeck { + ready_columns: vec![vec![], vec![], vec![]], + }, + 700_000, + ) + .expect("retry should restart"); + assert_eq!(retried.status, PuzzleClearRunStatus::Playing); + assert_eq!(retried.level_index, 1); + assert_eq!(retried.clears_done, 0); + } + + fn board_from_cards(size: u32, cards: Vec>) -> PuzzleClearBoard { + let mut cells = Vec::new(); + for row in 0..size { + for col in 0..size { + let index = (row * size + col) as usize; + cells.push(PuzzleClearCell { + row, + col, + card: cards.get(index).cloned().flatten(), + locked_group_id: None, + }); + } + } + PuzzleClearBoard { + rows: size, + cols: size, + cells, + } + } + + fn lock_cells(board: &mut PuzzleClearBoard, group_id: &str, positions: &[(u32, u32)]) { + for (row, col) in positions { + if let Some(cell) = cell_mut(board, *row, *col) { + cell.locked_group_id = Some(group_id.to_string()); + } + } + } + + fn card(group_id: &str, part_x: u32, part_y: u32) -> PuzzleClearCard { + card_shape(group_id, PuzzleClearShapeKind::OneByTwo, part_x, part_y) + } + + fn card_shape( + group_id: &str, + shape: PuzzleClearShapeKind, + part_x: u32, + part_y: u32, + ) -> PuzzleClearCard { + PuzzleClearCard { + card_id: format!("{group_id}-{part_x}-{part_y}"), + group_id: group_id.to_string(), + shape, + orientation: PuzzleClearOrientation::Horizontal, + part_x, + part_y, + image_src: format!("/{group_id}-{part_x}-{part_y}.png"), + image_object_key: format!("{group_id}-{part_x}-{part_y}.png"), + asset_object_id: format!("{group_id}-{part_x}-{part_y}-object"), + source_atlas_cell: format!("{group_id}:{part_x}:{part_y}"), + } + } +} diff --git a/server-rs/crates/module-puzzle-clear/src/commands.rs b/server-rs/crates/module-puzzle-clear/src/commands.rs new file mode 100644 index 00000000..66980ed7 --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/src/commands.rs @@ -0,0 +1,25 @@ +use shared_kernel::normalize_required_string; + +use crate::{PuzzleClearOrientation, PuzzleClearShapeKind}; + +pub fn parse_puzzle_clear_shape_kind(value: &str) -> PuzzleClearShapeKind { + match value.trim().to_ascii_lowercase().as_str() { + "1x3" | "one-by-three" => PuzzleClearShapeKind::OneByThree, + "2x2" | "two-by-two" => PuzzleClearShapeKind::TwoByTwo, + "2x3" | "two-by-three" => PuzzleClearShapeKind::TwoByThree, + _ => PuzzleClearShapeKind::OneByTwo, + } +} + +pub fn parse_puzzle_clear_orientation(value: &str) -> PuzzleClearOrientation { + match value.trim().to_ascii_lowercase().as_str() { + "vertical" | "纵向" => PuzzleClearOrientation::Vertical, + _ => PuzzleClearOrientation::Horizontal, + } +} + +pub fn normalize_puzzle_clear_seed(seed: &str, fallback: &str) -> String { + normalize_required_string(seed) + .or_else(|| normalize_required_string(fallback)) + .unwrap_or_else(|| "puzzle-clear".to_string()) +} diff --git a/server-rs/crates/module-puzzle-clear/src/domain.rs b/server-rs/crates/module-puzzle-clear/src/domain.rs new file mode 100644 index 00000000..4306d3a7 --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/src/domain.rs @@ -0,0 +1,191 @@ +use serde::{Deserialize, Serialize}; +#[cfg(feature = "spacetime-types")] +use spacetimedb::SpacetimeType; + +pub const PUZZLE_CLEAR_PLAY_ID: &str = "puzzle-clear"; +pub const PUZZLE_CLEAR_PUBLIC_WORK_CODE_PREFIX: &str = "PC-"; +pub const PUZZLE_CLEAR_SESSION_ID_PREFIX: &str = "puzzle-clear-session-"; +pub const PUZZLE_CLEAR_PROFILE_ID_PREFIX: &str = "puzzle-clear-profile-"; +pub const PUZZLE_CLEAR_WORK_ID_PREFIX: &str = "puzzle-clear-work-"; +pub const PUZZLE_CLEAR_RUN_ID_PREFIX: &str = "puzzle-clear-run-"; +pub const PUZZLE_CLEAR_LEVEL_DURATION_SECONDS: u32 = 600; + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum PuzzleClearShapeKind { + OneByTwo, + OneByThree, + TwoByTwo, + TwoByThree, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum PuzzleClearOrientation { + Horizontal, + Vertical, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum PuzzleClearRunStatus { + Playing, + LevelFailed, + LevelCleared, + Finished, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearLevelConfig { + pub level_index: u32, + pub board_size: u32, + pub target_clears: u32, + pub duration_seconds: u32, + pub unlocked_shapes: Vec, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearShapeQuota { + pub shape: PuzzleClearShapeKind, + pub count: u32, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearPatternGroup { + pub group_id: String, + pub shape: PuzzleClearShapeKind, + pub width: u32, + pub height: u32, + pub atlas_x: u32, + pub atlas_y: u32, + pub atlas_width: u32, + pub atlas_height: u32, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearCard { + pub card_id: String, + pub group_id: String, + pub shape: PuzzleClearShapeKind, + pub orientation: PuzzleClearOrientation, + pub part_x: u32, + pub part_y: u32, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub source_atlas_cell: String, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearCell { + pub row: u32, + pub col: u32, + pub card: Option, + pub locked_group_id: Option, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearBoard { + pub rows: u32, + pub cols: u32, + pub cells: Vec, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearDeck { + pub ready_columns: Vec>, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearMove { + pub from_row: u32, + pub from_col: u32, + pub to_row: u32, + pub to_col: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearElimination { + pub group_id: String, + pub positions: Vec<(u32, u32)>, +} + +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PuzzleClearRunSnapshot { + pub run_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub status: PuzzleClearRunStatus, + pub level_index: u32, + pub clears_done: u32, + pub board: PuzzleClearBoard, + pub deck: PuzzleClearDeck, + pub started_at_ms: u64, + pub level_started_at_ms: u64, + pub finished_at_ms: Option, +} + +impl PuzzleClearShapeKind { + pub fn as_str(self) -> &'static str { + match self { + Self::OneByTwo => "1x2", + Self::OneByThree => "1x3", + Self::TwoByTwo => "2x2", + Self::TwoByThree => "2x3", + } + } + + pub fn base_dimensions(self) -> (u32, u32) { + match self { + Self::OneByTwo => (2, 1), + Self::OneByThree => (3, 1), + Self::TwoByTwo => (2, 2), + Self::TwoByThree => (3, 2), + } + } + + pub fn dimensions(self, orientation: PuzzleClearOrientation) -> (u32, u32) { + let (width, height) = self.base_dimensions(); + if matches!(orientation, PuzzleClearOrientation::Vertical) + && matches!( + self, + PuzzleClearShapeKind::OneByTwo + | PuzzleClearShapeKind::OneByThree + | PuzzleClearShapeKind::TwoByThree + ) + { + (height, width) + } else { + (width, height) + } + } +} + +impl PuzzleClearOrientation { + pub fn as_str(self) -> &'static str { + match self { + Self::Horizontal => "horizontal", + Self::Vertical => "vertical", + } + } +} + +impl PuzzleClearRunStatus { + pub fn as_str(self) -> &'static str { + match self { + Self::Playing => "playing", + Self::LevelFailed => "level_failed", + Self::LevelCleared => "level_cleared", + Self::Finished => "finished", + } + } +} diff --git a/server-rs/crates/module-puzzle-clear/src/errors.rs b/server-rs/crates/module-puzzle-clear/src/errors.rs new file mode 100644 index 00000000..7b6fc69d --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/src/errors.rs @@ -0,0 +1,37 @@ +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PuzzleClearError { + MissingRunId, + MissingOwnerUserId, + MissingProfileId, + InvalidLevel, + InvalidBoard, + InvalidPosition, + EmptyDeck, + NoPlayableMove, + RunNotPlaying, + LevelExpired, + MissingCard, +} + +impl fmt::Display for PuzzleClearError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::MissingRunId => "puzzle-clear run_id 不能为空", + Self::MissingOwnerUserId => "puzzle-clear owner_user_id 不能为空", + Self::MissingProfileId => "puzzle-clear profile_id 不能为空", + Self::InvalidLevel => "puzzle-clear 关卡配置无效", + Self::InvalidBoard => "puzzle-clear 棋盘状态无效", + Self::InvalidPosition => "puzzle-clear 坐标无效", + Self::EmptyDeck => "puzzle-clear 发牌池为空", + Self::NoPlayableMove => "puzzle-clear 棋盘没有可解拼接", + Self::RunNotPlaying => "puzzle-clear 当前 run 不在 playing 状态", + Self::LevelExpired => "puzzle-clear 当前关卡已经超时", + Self::MissingCard => "puzzle-clear 目标格子没有卡牌", + }; + f.write_str(message) + } +} + +impl std::error::Error for PuzzleClearError {} diff --git a/server-rs/crates/module-puzzle-clear/src/events.rs b/server-rs/crates/module-puzzle-clear/src/events.rs new file mode 100644 index 00000000..1e60ddbb --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/src/events.rs @@ -0,0 +1,31 @@ +//! 拼消消领域事件。 +//! +//! 事件只表达已经发生的领域事实,持久化、统计投影和前端通知由 +//! SpacetimeDB adapter 与 BFF 编排层决定。 + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PuzzleClearDomainEvent { + DraftCompiled { + profile_id: String, + owner_user_id: String, + occurred_at_micros: i64, + }, + WorkPublished { + profile_id: String, + owner_user_id: String, + occurred_at_micros: i64, + }, + LevelCleared { + run_id: String, + owner_user_id: String, + level_index: u32, + clears_done: u32, + occurred_at_micros: i64, + }, + RunSettled { + run_id: String, + owner_user_id: String, + status: String, + occurred_at_micros: i64, + }, +} diff --git a/server-rs/crates/module-puzzle-clear/src/lib.rs b/server-rs/crates/module-puzzle-clear/src/lib.rs new file mode 100644 index 00000000..6acdc7c7 --- /dev/null +++ b/server-rs/crates/module-puzzle-clear/src/lib.rs @@ -0,0 +1,11 @@ +mod application; +mod commands; +mod domain; +mod errors; +mod events; + +pub use application::*; +pub use commands::*; +pub use domain::*; +pub use errors::*; +pub use events::*; diff --git a/server-rs/crates/module-runtime/src/application.rs b/server-rs/crates/module-runtime/src/application.rs index c6729d1c..2c578dd9 100644 --- a/server-rs/crates/module-runtime/src/application.rs +++ b/server-rs/crates/module-runtime/src/application.rs @@ -80,7 +80,7 @@ pub fn default_creation_entry_event_banner_snapshots() -> Vec

创作公告

这里可以在后台替换成你的公告 HTML。

"# + r#"

创作公告

这里可以在后台替换成你的公告 HTML。

"# .to_string(), ), }] @@ -233,11 +233,16 @@ pub fn resolve_creation_entry_event_banner_responses( event_banners_json: Option<&str>, fallback_banner: &CreationEntryEventBannerSnapshot, ) -> Vec { - event_banners_json + let banners = event_banners_json .and_then(|raw| decode_creation_entry_event_banner_snapshots(raw).ok()) .filter(|banners| !banners.is_empty()) - .unwrap_or_else(|| vec![fallback_banner.clone()]) - .into_iter() + .unwrap_or_else(default_creation_entry_event_banner_snapshots); + if banners.is_empty() { + vec![fallback_banner.clone()] + } else { + banners + } + .into_iter() .map(build_creation_entry_event_banner_response) .collect() } @@ -410,6 +415,20 @@ pub fn default_creation_entry_type_snapshots( 20, updated_at_micros, ), + build_default_creation_entry_type_snapshot( + "puzzle-clear", + "拼消消", + "拼接消除玩法", + "可创建", + "/creation-type-references/puzzle.webp", + true, + true, + 46, + "recommended", + "热门推荐", + 20, + updated_at_micros, + ), build_default_creation_entry_type_snapshot( "wooden-fish", "敲木鱼", diff --git a/server-rs/crates/module-runtime/src/domain.rs b/server-rs/crates/module-runtime/src/domain.rs index d421ce60..32e6a1b7 100644 --- a/server-rs/crates/module-runtime/src/domain.rs +++ b/server-rs/crates/module-runtime/src/domain.rs @@ -57,7 +57,7 @@ pub const DEFAULT_CREATION_ENTRY_CATEGORY_LABEL: &str = "热门推荐"; pub const DEFAULT_CREATION_ENTRY_EVENT_TITLE: &str = "主题创作赛"; pub const DEFAULT_CREATION_ENTRY_EVENT_DESCRIPTION: &str = "用温暖的色彩,捏出秋天的故事。"; pub const DEFAULT_CREATION_ENTRY_EVENT_COVER_IMAGE_SRC: &str = - "/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png"; + "/creation-type-references/puzzle.webp"; pub const DEFAULT_CREATION_ENTRY_EVENT_PRIZE_POOL_MUD_POINTS: u64 = 58_000; pub const DEFAULT_CREATION_ENTRY_EVENT_STARTS_AT_TEXT: &str = "2024.10.20 10:00"; pub const DEFAULT_CREATION_ENTRY_EVENT_ENDS_AT_TEXT: &str = "2024.11.20 23:59"; diff --git a/server-rs/crates/module-runtime/src/lib.rs b/server-rs/crates/module-runtime/src/lib.rs index 64811424..3e16b6fe 100644 --- a/server-rs/crates/module-runtime/src/lib.rs +++ b/server-rs/crates/module-runtime/src/lib.rs @@ -319,6 +319,35 @@ mod tests { assert_eq!(banners, default_creation_entry_event_banner_snapshots()); } + #[test] + fn creation_entry_event_banners_none_returns_default_announcements() { + let legacy_banner = CreationEntryEventBannerSnapshot { + title: "旧结构化横幅".to_string(), + description: "旧库单条字段".to_string(), + cover_image_src: + "/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png" + .to_string(), + prize_pool_mud_points: 58_000, + starts_at_text: "2024.10.20 10:00".to_string(), + ends_at_text: "2024.11.20 23:59".to_string(), + render_mode: "structured".to_string(), + html_code: None, + }; + + let banners = resolve_creation_entry_event_banner_responses(None, &legacy_banner); + + assert_eq!(banners.len(), 1); + assert_eq!(banners[0].render_mode, "html"); + assert_eq!(banners[0].title, "创作公告"); + assert!(banners[0].html_code.as_deref().unwrap_or("").contains("创作公告")); + assert!(banners[0] + .html_code + .as_deref() + .unwrap_or("") + .contains("/creation-type-references/puzzle.webp")); + assert_ne!(banners[0].cover_image_src, legacy_banner.cover_image_src); + } + #[test] fn creation_entry_event_banners_json_accepts_announcement_html_code() { let normalized = normalize_creation_entry_event_banners_json( @@ -417,6 +446,22 @@ mod tests { assert_eq!(wooden_fish.image_src, "/wooden-fish/default-hit-object.png"); } + #[test] + fn default_creation_entry_types_include_puzzle_clear() { + let configs = default_creation_entry_type_snapshots(1); + let puzzle_clear = configs + .iter() + .find(|item| item.id == "puzzle-clear") + .expect("puzzle-clear creation entry should be seeded"); + + assert_eq!(puzzle_clear.title, "拼消消"); + assert!(puzzle_clear.visible); + assert!(puzzle_clear.open); + assert_eq!(puzzle_clear.badge, "可创建"); + assert_eq!(puzzle_clear.sort_order, 46); + assert_eq!(puzzle_clear.category_id, "recommended"); + } + #[test] fn default_creation_entry_types_include_jump_hop_theme_only_entry() { let configs = default_creation_entry_type_snapshots(1); diff --git a/server-rs/crates/platform-oss/src/lib.rs b/server-rs/crates/platform-oss/src/lib.rs index 656db455..31d48a1b 100644 --- a/server-rs/crates/platform-oss/src/lib.rs +++ b/server-rs/crates/platform-oss/src/lib.rs @@ -22,7 +22,7 @@ const OSS_V4_SERVICE: &str = "oss"; const OSS_UNSIGNED_PAYLOAD: &str = "UNSIGNED-PAYLOAD"; const OSS_PROVIDER: &str = "aliyun-oss"; -pub const LEGACY_PUBLIC_PREFIXES: [&str; 13] = [ +pub const LEGACY_PUBLIC_PREFIXES: [&str; 14] = [ "generated-character-drafts", "generated-characters", "generated-animations", @@ -31,6 +31,7 @@ pub const LEGACY_PUBLIC_PREFIXES: [&str; 13] = [ "generated-wooden-fish-assets", "generated-match3d-assets", "generated-puzzle-assets", + "generated-puzzle-clear-assets", "generated-jump-hop-assets", "generated-custom-world-scenes", "generated-custom-world-covers", @@ -55,6 +56,7 @@ pub enum LegacyAssetPrefix { WoodenFishAssets, Match3DAssets, PuzzleAssets, + PuzzleClearAssets, JumpHopAssets, CustomWorldScenes, CustomWorldCovers, @@ -245,6 +247,7 @@ impl LegacyAssetPrefix { "generated-wooden-fish-assets" => Some(Self::WoodenFishAssets), "generated-match3d-assets" => Some(Self::Match3DAssets), "generated-puzzle-assets" => Some(Self::PuzzleAssets), + "generated-puzzle-clear-assets" => Some(Self::PuzzleClearAssets), "generated-jump-hop-assets" => Some(Self::JumpHopAssets), "generated-custom-world-scenes" => Some(Self::CustomWorldScenes), "generated-custom-world-covers" => Some(Self::CustomWorldCovers), @@ -264,6 +267,7 @@ impl LegacyAssetPrefix { Self::WoodenFishAssets => "generated-wooden-fish-assets", Self::Match3DAssets => "generated-match3d-assets", Self::PuzzleAssets => "generated-puzzle-assets", + Self::PuzzleClearAssets => "generated-puzzle-clear-assets", Self::JumpHopAssets => "generated-jump-hop-assets", Self::CustomWorldScenes => "generated-custom-world-scenes", Self::CustomWorldCovers => "generated-custom-world-covers", diff --git a/server-rs/crates/shared-contracts/src/auth.rs b/server-rs/crates/shared-contracts/src/auth.rs index 94847336..0c0f5809 100644 --- a/server-rs/crates/shared-contracts/src/auth.rs +++ b/server-rs/crates/shared-contracts/src/auth.rs @@ -19,10 +19,13 @@ pub struct AuthUserPayload { pub public_user_code: String, pub display_name: String, pub avatar_url: Option, + pub phone_number: Option, pub phone_number_masked: Option, pub login_method: String, pub binding_status: String, pub wechat_bound: bool, + pub wechat_display_name: Option, + pub wechat_account: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] diff --git a/server-rs/crates/shared-contracts/src/lib.rs b/server-rs/crates/shared-contracts/src/lib.rs index 2f13b4be..4faea2da 100644 --- a/server-rs/crates/shared-contracts/src/lib.rs +++ b/server-rs/crates/shared-contracts/src/lib.rs @@ -19,6 +19,7 @@ pub mod match3d_runtime; pub mod match3d_works; pub mod public_work; pub mod puzzle_agent; +pub mod puzzle_clear; pub mod puzzle_creative_template; pub mod puzzle_gallery; pub mod puzzle_runtime; diff --git a/server-rs/crates/shared-contracts/src/puzzle_clear.rs b/server-rs/crates/shared-contracts/src/puzzle_clear.rs new file mode 100644 index 00000000..9d2af4f2 --- /dev/null +++ b/server-rs/crates/shared-contracts/src/puzzle_clear.rs @@ -0,0 +1,313 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PuzzleClearGenerationStatus { + Draft, + Generating, + Ready, + Failed, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum PuzzleClearActionType { + CompileDraft, + RegenerateAtlas, + UpdateWorkMeta, + UpdateBoardBackground, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PuzzleClearRunStatus { + Playing, + LevelFailed, + LevelCleared, + Finished, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearImageAsset { + pub asset_id: String, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub generation_provider: String, + pub prompt: String, + pub width: u32, + pub height: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearPatternGroup { + pub group_id: String, + pub shape: String, + pub width: u32, + pub height: u32, + pub atlas_x: u32, + pub atlas_y: u32, + pub atlas_width: u32, + pub atlas_height: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearCardAsset { + pub card_id: String, + pub group_id: String, + pub shape: String, + pub orientation: String, + pub part_x: u32, + pub part_y: u32, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub source_atlas_cell: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorkspaceCreateRequest { + pub template_id: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + #[serde(default)] + pub board_background_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearActionRequest { + pub action_type: PuzzleClearActionType, + pub profile_id: Option, + pub work_title: Option, + pub work_description: Option, + pub theme_prompt: Option, + #[serde(default)] + pub board_background_prompt: Option, + pub generate_board_background: Option, + pub board_background_asset: Option, + pub atlas_asset: Option, + pub pattern_groups: Option>, + pub card_assets: Option>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearDraftResponse { + pub template_id: String, + pub template_name: String, + pub profile_id: Option, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + #[serde(default)] + pub board_background_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + pub card_back_image_src: Option, + pub atlas_asset: Option, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub generation_status: PuzzleClearGenerationStatus, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearSessionSnapshotResponse { + pub session_id: String, + pub owner_user_id: String, + pub status: PuzzleClearGenerationStatus, + pub draft: Option, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearSessionResponse { + pub session: PuzzleClearSessionSnapshotResponse, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearActionResponse { + pub action_type: PuzzleClearActionType, + pub session: PuzzleClearSessionSnapshotResponse, + pub work: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorkSummaryResponse { + pub runtime_kind: String, + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub source_session_id: Option, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub cover_image_src: Option, + pub publication_status: String, + pub play_count: u32, + pub updated_at: String, + pub published_at: Option, + pub publish_ready: bool, + pub generation_status: PuzzleClearGenerationStatus, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorkProfileResponse { + pub summary: PuzzleClearWorkSummaryResponse, + pub draft: PuzzleClearDraftResponse, + pub board_background_asset: Option, + pub atlas_asset: PuzzleClearImageAsset, + pub pattern_groups: Vec, + pub card_assets: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorksResponse { + pub items: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorkDetailResponse { + pub item: PuzzleClearWorkProfileResponse, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorkMutationResponse { + pub item: PuzzleClearWorkProfileResponse, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearBoardCell { + pub row: u32, + pub col: u32, + pub card: Option, + pub locked_group_id: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearBoardSnapshot { + pub rows: u32, + pub cols: u32, + pub cells: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearRuntimeSnapshotResponse { + pub run_id: String, + pub profile_id: String, + pub owner_user_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub runtime_mode: Option, + pub status: PuzzleClearRunStatus, + pub level_index: u32, + pub clears_done: u32, + pub target_clears: u32, + pub level_duration_seconds: u32, + pub level_started_at_ms: u64, + pub board: PuzzleClearBoardSnapshot, + pub ready_columns: Vec>, + pub started_at_ms: u64, + pub finished_at_ms: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearRunResponse { + pub run: PuzzleClearRuntimeSnapshotResponse, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearStartRunRequest { + pub profile_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearSwapRequest { + pub from_row: u32, + pub from_col: u32, + pub to_row: u32, + pub to_col: u32, + pub client_action_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearRetryLevelRequest { + pub client_action_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearNextLevelRequest { + pub client_action_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearTimeUpRequest { + pub client_action_id: String, +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn workspace_create_request_uses_camel_case() { + let payload = PuzzleClearWorkspaceCreateRequest { + template_id: "puzzle-clear".to_string(), + work_title: "花园拼消消".to_string(), + work_description: "轻松消除".to_string(), + theme_prompt: "春日花园".to_string(), + board_background_prompt: "樱花庭院".to_string(), + generate_board_background: true, + board_background_asset: None, + }; + + let value = serde_json::to_value(payload).expect("request should serialize"); + + assert_eq!(value["templateId"], json!("puzzle-clear")); + assert_eq!(value["themePrompt"], json!("春日花园")); + assert_eq!(value["boardBackgroundPrompt"], json!("樱花庭院")); + assert_eq!(value["generateBoardBackground"], json!(true)); + } + + #[test] + fn runtime_swap_request_uses_camel_case() { + let payload = PuzzleClearSwapRequest { + from_row: 1, + from_col: 2, + to_row: 1, + to_col: 3, + client_action_id: "swap-1".to_string(), + }; + + let value = serde_json::to_value(payload).expect("request should serialize"); + + assert_eq!(value["fromRow"], json!(1)); + assert_eq!(value["toCol"], json!(3)); + assert_eq!(value["clientActionId"], json!("swap-1")); + } +} diff --git a/server-rs/crates/spacetime-client/Cargo.toml b/server-rs/crates/spacetime-client/Cargo.toml index fcb0c9ce..00017607 100644 --- a/server-rs/crates/spacetime-client/Cargo.toml +++ b/server-rs/crates/spacetime-client/Cargo.toml @@ -16,6 +16,7 @@ module-wooden-fish = { workspace = true } module-match3d = { workspace = true } module-npc = { workspace = true } module-puzzle = { workspace = true } +module-puzzle-clear = { workspace = true } module-runtime = { workspace = true } module-runtime-story = { workspace = true } module-runtime-item = { workspace = true } diff --git a/server-rs/crates/spacetime-client/src/bark_battle.rs b/server-rs/crates/spacetime-client/src/bark_battle.rs index 1f47242c..99482684 100644 --- a/server-rs/crates/spacetime-client/src/bark_battle.rs +++ b/server-rs/crates/spacetime-client/src/bark_battle.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; pub type BarkBattleDraftCreateRecordInput = BarkBattleDraftCreateInput; pub type BarkBattleDraftConfigUpsertRecordInput = BarkBattleDraftConfigUpsertInput; +pub type BarkBattleWorkDeleteRecordInput = BarkBattleWorkDeleteInput; pub type BarkBattleWorkPublishRecordInput = BarkBattleWorkPublishInput; pub type BarkBattleRunStartRecordInput = BarkBattleRunStartInput; pub type BarkBattleRunFinishRecordInput = BarkBattleRunFinishInput; @@ -88,6 +89,34 @@ impl SpacetimeClient { .await } + pub async fn delete_bark_battle_work( + &self, + input: BarkBattleWorkDeleteRecordInput, + ) -> Result, SpacetimeClientError> { + let owner_user_id = input.owner_user_id.clone(); + self.call_after_connect("delete_bark_battle_work", move |connection, sender| { + connection + .procedures() + .delete_bark_battle_work_then(input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(|result| { + if result.ok { + Ok(()) + } else { + Err(SpacetimeClientError::procedure_failed( + result.error_message, + )) + } + }); + send_once(&sender, mapped); + }); + }) + .await?; + + self.list_bark_battle_works(owner_user_id).await + } + pub async fn get_bark_battle_runtime_config( &self, work_id: String, diff --git a/server-rs/crates/spacetime-client/src/jump_hop.rs b/server-rs/crates/spacetime-client/src/jump_hop.rs index 8470345b..df224a68 100644 --- a/server-rs/crates/spacetime-client/src/jump_hop.rs +++ b/server-rs/crates/spacetime-client/src/jump_hop.rs @@ -222,6 +222,30 @@ impl SpacetimeClient { .await } + pub async fn delete_jump_hop_work( + &self, + profile_id: String, + owner_user_id: String, + ) -> Result, SpacetimeClientError> { + let procedure_input = JumpHopWorkDeleteInput { + profile_id, + owner_user_id, + }; + + self.call_after_connect("delete_jump_hop_work", move |connection, sender| { + connection.procedures().delete_jump_hop_work_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_jump_hop_works_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + pub async fn get_jump_hop_runtime_work( &self, profile_id: String, diff --git a/server-rs/crates/spacetime-client/src/lib.rs b/server-rs/crates/spacetime-client/src/lib.rs index 73e0b55f..bb0c0a13 100644 --- a/server-rs/crates/spacetime-client/src/lib.rs +++ b/server-rs/crates/spacetime-client/src/lib.rs @@ -52,16 +52,24 @@ pub use mapper::{ PuzzleAgentMessageSubmitRecordInput, PuzzleAgentSessionCreateRecordInput, PuzzleAgentSessionRecord, PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord, PuzzleAnchorPackRecord, PuzzleAudioAssetRecord, PuzzleBoardRecord, PuzzleCellPositionRecord, - PuzzleCreatorIntentRecord, PuzzleDraftCompileFailureRecordInput, PuzzleDraftLevelRecord, - PuzzleFormDraftRecord, PuzzleFormDraftSaveRecordInput, PuzzleGalleryCardRecord, - PuzzleGeneratedImageCandidateRecord, PuzzleGeneratedImagesSaveRecordInput, - PuzzleLeaderboardEntryRecord, PuzzleLeaderboardSubmitRecordInput, PuzzleMergedGroupRecord, - PuzzlePieceStateRecord, PuzzlePublishRecordInput, PuzzleRecommendedNextWorkRecord, - PuzzleResultDraftRecord, PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, - PuzzleResultPreviewRecord, PuzzleRunDragRecordInput, PuzzleRunNextLevelRecordInput, - PuzzleRunPauseRecordInput, PuzzleRunPropRecordInput, PuzzleRunRecord, - PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput, PuzzleRuntimeLevelRecord, - PuzzleSelectCoverImageRecordInput, PuzzleUiBackgroundSaveRecordInput, + PuzzleClearActionRequest, PuzzleClearActionResponse, PuzzleClearActionType, + PuzzleClearBoardCell, PuzzleClearBoardSnapshot, PuzzleClearCardAsset, PuzzleClearDraftResponse, + PuzzleClearGenerationStatus, PuzzleClearImageAsset, PuzzleClearNextLevelRequest, + PuzzleClearPatternGroup, PuzzleClearRetryLevelRequest, PuzzleClearRunResponse, + PuzzleClearRunStatus, PuzzleClearRuntimeSnapshotResponse, PuzzleClearSessionResponse, + PuzzleClearSessionSnapshotResponse, PuzzleClearStartRunRequest, PuzzleClearSwapRequest, + PuzzleClearTimeUpRequest, PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse, + PuzzleClearWorkProfileResponse, PuzzleClearWorkSummaryResponse, PuzzleClearWorksResponse, + PuzzleClearWorkspaceCreateRequest, PuzzleCreatorIntentRecord, + PuzzleDraftCompileFailureRecordInput, PuzzleDraftLevelRecord, PuzzleFormDraftRecord, + PuzzleFormDraftSaveRecordInput, PuzzleGalleryCardRecord, PuzzleGeneratedImageCandidateRecord, + PuzzleGeneratedImagesSaveRecordInput, PuzzleLeaderboardEntryRecord, + PuzzleLeaderboardSubmitRecordInput, PuzzleMergedGroupRecord, PuzzlePieceStateRecord, + PuzzlePublishRecordInput, PuzzleRecommendedNextWorkRecord, PuzzleResultDraftRecord, + PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, PuzzleResultPreviewRecord, + PuzzleRunDragRecordInput, PuzzleRunNextLevelRecordInput, PuzzleRunPauseRecordInput, + PuzzleRunPropRecordInput, PuzzleRunRecord, PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput, + PuzzleRuntimeLevelRecord, PuzzleSelectCoverImageRecordInput, PuzzleUiBackgroundSaveRecordInput, PuzzleWorkLikeReportRecordInput, PuzzleWorkPointIncentiveClaimRecordInput, PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput, ResolveCombatActionRecord, ResolveNpcBattleInteractionInput, @@ -98,7 +106,7 @@ pub mod bark_battle; pub use bark_battle::{ BarkBattleDraftConfigUpsertRecordInput, BarkBattleDraftCreateRecordInput, BarkBattleRunFinishRecordInput, BarkBattleRunStartRecordInput, - BarkBattleWorkPublishRecordInput, + BarkBattleWorkDeleteRecordInput, BarkBattleWorkPublishRecordInput, }; pub mod big_fish; pub mod combat; @@ -109,6 +117,7 @@ pub mod match3d; pub mod npc; pub mod public_work; pub mod puzzle; +pub mod puzzle_clear; pub mod runtime; pub mod square_hole; pub mod story; @@ -575,6 +584,7 @@ impl SpacetimeClient { "SELECT * FROM public_work_detail_entry", "SELECT * FROM bark_battle_gallery_view", "SELECT * FROM puzzle_gallery_card_view", + "SELECT * FROM puzzle_clear_gallery_card_view", "SELECT * FROM jump_hop_gallery_card_view", "SELECT * FROM wooden_fish_gallery_card_view", "SELECT * FROM custom_world_gallery_entry", @@ -591,6 +601,7 @@ impl SpacetimeClient { for query in [ "SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'", + "SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle-clear'", "SELECT * FROM public_work_play_daily_stat WHERE source_type = 'jump-hop'", "SELECT * FROM public_work_play_daily_stat WHERE source_type = 'wooden-fish'", "SELECT * FROM public_work_play_daily_stat WHERE source_type = 'custom-world'", diff --git a/server-rs/crates/spacetime-client/src/mapper.rs b/server-rs/crates/spacetime-client/src/mapper.rs index 271f1be4..c1f4c069 100644 --- a/server-rs/crates/spacetime-client/src/mapper.rs +++ b/server-rs/crates/spacetime-client/src/mapper.rs @@ -14,6 +14,7 @@ mod match3d; mod npc; mod public_work; mod puzzle; +mod puzzle_clear; mod runtime; mod runtime_profile; mod square_hole; @@ -114,6 +115,17 @@ pub use self::puzzle::{ PuzzleWorkLikeReportRecordInput, PuzzleWorkPointIncentiveClaimRecordInput, PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput, }; +pub use self::puzzle_clear::{ + PuzzleClearActionRequest, PuzzleClearActionResponse, PuzzleClearActionType, + PuzzleClearBoardCell, PuzzleClearBoardSnapshot, PuzzleClearCardAsset, PuzzleClearDraftResponse, + PuzzleClearGenerationStatus, PuzzleClearImageAsset, PuzzleClearNextLevelRequest, + PuzzleClearPatternGroup, PuzzleClearRetryLevelRequest, PuzzleClearRunResponse, + PuzzleClearRunStatus, PuzzleClearRuntimeSnapshotResponse, PuzzleClearSessionResponse, + PuzzleClearSessionSnapshotResponse, PuzzleClearStartRunRequest, PuzzleClearSwapRequest, + PuzzleClearTimeUpRequest, PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse, + PuzzleClearWorkProfileResponse, PuzzleClearWorkSummaryResponse, PuzzleClearWorksResponse, + PuzzleClearWorkspaceCreateRequest, +}; pub use self::runtime::{ AdminWorkVisibilityRecord, BigFishGameDraftRecord, BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord, BigFishRuntimeRunRecord, CreationEntryConfigRecord, @@ -192,6 +204,11 @@ pub(crate) use self::puzzle::{ map_puzzle_works_procedure_result, map_runtime_profile_wallet_ledger_source_type_back, parse_puzzle_agent_stage_record, }; +pub(crate) use self::puzzle_clear::{ + map_puzzle_clear_agent_session_procedure_result, map_puzzle_clear_gallery_card_view_row, + map_puzzle_clear_run_procedure_result, map_puzzle_clear_work_procedure_result, + map_puzzle_clear_works_procedure_result, +}; pub(crate) use self::runtime::{ build_admin_work_visibility_list_input, build_admin_work_visibility_update_input, build_creation_entry_config_record_from_rows, map_admin_work_visibility_list_procedure_result, diff --git a/server-rs/crates/spacetime-client/src/mapper/puzzle_clear.rs b/server-rs/crates/spacetime-client/src/mapper/puzzle_clear.rs new file mode 100644 index 00000000..d67a96a1 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/mapper/puzzle_clear.rs @@ -0,0 +1,289 @@ +use super::*; +pub use shared_contracts::puzzle_clear::{ + PuzzleClearActionRequest, PuzzleClearActionResponse, PuzzleClearActionType, + PuzzleClearBoardCell, PuzzleClearBoardSnapshot, PuzzleClearCardAsset, PuzzleClearDraftResponse, + PuzzleClearGenerationStatus, PuzzleClearImageAsset, PuzzleClearNextLevelRequest, + PuzzleClearPatternGroup, PuzzleClearRetryLevelRequest, PuzzleClearRunResponse, + PuzzleClearRunStatus, PuzzleClearRuntimeSnapshotResponse, PuzzleClearSessionResponse, + PuzzleClearSessionSnapshotResponse, PuzzleClearStartRunRequest, PuzzleClearSwapRequest, + PuzzleClearTimeUpRequest, PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse, + PuzzleClearWorkProfileResponse, PuzzleClearWorkSummaryResponse, PuzzleClearWorksResponse, + PuzzleClearWorkspaceCreateRequest, +}; + +pub(crate) fn map_puzzle_clear_agent_session_procedure_result( + result: PuzzleClearAgentSessionProcedureResult, +) -> Result { + if !result.ok { + return Err(SpacetimeClientError::procedure_failed(result.error_message)); + } + let session = result + .session + .ok_or_else(|| SpacetimeClientError::missing_snapshot("puzzle clear agent session 快照"))?; + Ok(map_puzzle_clear_session_snapshot(session)) +} + +pub(crate) fn map_puzzle_clear_work_procedure_result( + result: PuzzleClearWorkProcedureResult, +) -> Result { + if !result.ok { + return Err(SpacetimeClientError::procedure_failed(result.error_message)); + } + let work = result + .work + .ok_or_else(|| SpacetimeClientError::missing_snapshot("puzzle clear work 快照"))?; + Ok(map_puzzle_clear_work_snapshot(work)) +} + +pub(crate) fn map_puzzle_clear_works_procedure_result( + result: PuzzleClearWorksProcedureResult, +) -> Result, SpacetimeClientError> { + if !result.ok { + return Err(SpacetimeClientError::procedure_failed(result.error_message)); + } + Ok(result + .items + .into_iter() + .map(map_puzzle_clear_work_snapshot) + .collect()) +} + +pub(crate) fn map_puzzle_clear_run_procedure_result( + result: PuzzleClearRunProcedureResult, +) -> Result { + if !result.ok { + return Err(SpacetimeClientError::procedure_failed(result.error_message)); + } + let run = result + .run + .ok_or_else(|| SpacetimeClientError::missing_snapshot("puzzle clear run 快照"))?; + Ok(map_puzzle_clear_run_snapshot(run)) +} + +pub(crate) fn map_puzzle_clear_gallery_card_view_row( + row: PuzzleClearGalleryCardViewRow, +) -> PuzzleClearWorkSummaryResponse { + PuzzleClearWorkSummaryResponse { + runtime_kind: "puzzle-clear".to_string(), + work_id: row.work_id, + profile_id: row.profile_id, + owner_user_id: row.owner_user_id, + source_session_id: None, + work_title: row.work_title, + work_description: row.work_description, + theme_prompt: row.theme_prompt, + cover_image_src: row.cover_image_src, + publication_status: normalize_publication_status(&row.publication_status).to_string(), + play_count: row.play_count, + updated_at: format_timestamp_micros(row.updated_at_micros), + published_at: row.published_at_micros.map(format_timestamp_micros), + publish_ready: true, + generation_status: parse_generation_status(&row.generation_status), + } +} + +fn map_puzzle_clear_session_snapshot( + snapshot: PuzzleClearAgentSessionSnapshot, +) -> PuzzleClearSessionSnapshotResponse { + PuzzleClearSessionSnapshotResponse { + session_id: snapshot.session_id, + owner_user_id: snapshot.owner_user_id, + status: parse_generation_status(&snapshot.status), + draft: snapshot.draft.map(map_puzzle_clear_draft_snapshot), + created_at: format_timestamp_micros(snapshot.created_at_micros), + updated_at: format_timestamp_micros(snapshot.updated_at_micros), + } +} + +fn map_puzzle_clear_work_snapshot( + snapshot: PuzzleClearWorkSnapshot, +) -> PuzzleClearWorkProfileResponse { + let atlas_asset = map_image_asset(snapshot.atlas_asset.clone()); + let pattern_groups = snapshot + .pattern_groups + .clone() + .into_iter() + .map(map_pattern_group) + .collect::>(); + let card_assets = snapshot + .card_assets + .clone() + .into_iter() + .map(map_card_asset) + .collect::>(); + let board_background_asset = snapshot.board_background_asset.clone().map(map_image_asset); + let draft = PuzzleClearDraftResponse { + template_id: "puzzle-clear".to_string(), + template_name: "拼消消".to_string(), + profile_id: Some(snapshot.profile_id.clone()), + work_title: snapshot.work_title.clone(), + work_description: snapshot.work_description.clone(), + theme_prompt: snapshot.theme_prompt.clone(), + board_background_prompt: snapshot.board_background_prompt.clone(), + generate_board_background: snapshot.generate_board_background, + board_background_asset: board_background_asset.clone(), + card_back_image_src: snapshot.card_back_image_src.clone(), + atlas_asset: Some(atlas_asset.clone()), + pattern_groups: pattern_groups.clone(), + card_assets: card_assets.clone(), + generation_status: parse_generation_status(&snapshot.generation_status), + }; + + PuzzleClearWorkProfileResponse { + summary: PuzzleClearWorkSummaryResponse { + runtime_kind: "puzzle-clear".to_string(), + work_id: snapshot.work_id, + profile_id: snapshot.profile_id, + owner_user_id: snapshot.owner_user_id, + source_session_id: empty_string_to_none(snapshot.source_session_id), + work_title: snapshot.work_title, + work_description: snapshot.work_description, + theme_prompt: snapshot.theme_prompt, + cover_image_src: snapshot.cover_image_src, + publication_status: normalize_publication_status(&snapshot.publication_status) + .to_string(), + play_count: snapshot.play_count, + updated_at: format_timestamp_micros(snapshot.updated_at_micros), + published_at: snapshot.published_at_micros.map(format_timestamp_micros), + publish_ready: snapshot.publish_ready, + generation_status: parse_generation_status(&snapshot.generation_status), + }, + draft, + board_background_asset, + atlas_asset, + pattern_groups, + card_assets, + } +} + +fn map_puzzle_clear_draft_snapshot(snapshot: PuzzleClearDraftSnapshot) -> PuzzleClearDraftResponse { + PuzzleClearDraftResponse { + template_id: snapshot.template_id, + template_name: snapshot.template_name, + profile_id: snapshot.profile_id, + work_title: snapshot.work_title, + work_description: snapshot.work_description, + theme_prompt: snapshot.theme_prompt, + board_background_prompt: snapshot.board_background_prompt, + generate_board_background: snapshot.generate_board_background, + board_background_asset: snapshot.board_background_asset.map(map_image_asset), + card_back_image_src: snapshot.card_back_image_src, + atlas_asset: snapshot.atlas_asset.map(map_image_asset), + pattern_groups: snapshot + .pattern_groups + .into_iter() + .map(map_pattern_group) + .collect(), + card_assets: snapshot + .card_assets + .into_iter() + .map(map_card_asset) + .collect(), + generation_status: parse_generation_status(&snapshot.generation_status), + } +} + +fn map_image_asset(snapshot: PuzzleClearImageAssetSnapshot) -> PuzzleClearImageAsset { + PuzzleClearImageAsset { + asset_id: snapshot.asset_id, + image_src: snapshot.image_src, + image_object_key: snapshot.image_object_key, + asset_object_id: snapshot.asset_object_id, + generation_provider: snapshot.generation_provider, + prompt: snapshot.prompt, + width: snapshot.width, + height: snapshot.height, + } +} + +fn map_pattern_group(snapshot: PuzzleClearPatternGroupSnapshot) -> PuzzleClearPatternGroup { + PuzzleClearPatternGroup { + group_id: snapshot.group_id, + shape: snapshot.shape, + width: snapshot.width, + height: snapshot.height, + atlas_x: snapshot.atlas_x, + atlas_y: snapshot.atlas_y, + atlas_width: snapshot.atlas_width, + atlas_height: snapshot.atlas_height, + } +} + +fn map_card_asset(snapshot: PuzzleClearCardAssetSnapshot) -> PuzzleClearCardAsset { + PuzzleClearCardAsset { + card_id: snapshot.card_id, + group_id: snapshot.group_id, + shape: snapshot.shape, + orientation: snapshot.orientation, + part_x: snapshot.part_x, + part_y: snapshot.part_y, + image_src: snapshot.image_src, + image_object_key: snapshot.image_object_key, + asset_object_id: snapshot.asset_object_id, + source_atlas_cell: snapshot.source_atlas_cell, + } +} + +fn map_puzzle_clear_run_snapshot( + snapshot: PuzzleClearRuntimeSnapshot, +) -> PuzzleClearRuntimeSnapshotResponse { + PuzzleClearRuntimeSnapshotResponse { + run_id: snapshot.run_id, + profile_id: snapshot.profile_id, + owner_user_id: snapshot.owner_user_id, + runtime_mode: None, + status: parse_run_status(&snapshot.status), + level_index: snapshot.level_index, + clears_done: snapshot.clears_done, + target_clears: snapshot.target_clears, + level_duration_seconds: snapshot.level_duration_seconds, + level_started_at_ms: snapshot.level_started_at_ms, + board: PuzzleClearBoardSnapshot { + rows: snapshot.board.rows, + cols: snapshot.board.cols, + cells: snapshot + .board + .cells + .into_iter() + .map(|cell| PuzzleClearBoardCell { + row: cell.row, + col: cell.col, + card: cell.card.map(map_card_asset), + locked_group_id: cell.locked_group_id, + }) + .collect(), + }, + ready_columns: snapshot + .ready_columns + .into_iter() + .map(|column| column.into_iter().map(map_card_asset).collect()) + .collect(), + started_at_ms: snapshot.started_at_ms, + finished_at_ms: snapshot.finished_at_ms, + } +} + +fn parse_generation_status(value: &str) -> PuzzleClearGenerationStatus { + match value { + "generating" => PuzzleClearGenerationStatus::Generating, + "ready" => PuzzleClearGenerationStatus::Ready, + "failed" => PuzzleClearGenerationStatus::Failed, + _ => PuzzleClearGenerationStatus::Draft, + } +} + +fn parse_run_status(value: &str) -> PuzzleClearRunStatus { + match value { + "level_failed" => PuzzleClearRunStatus::LevelFailed, + "level_cleared" => PuzzleClearRunStatus::LevelCleared, + "finished" => PuzzleClearRunStatus::Finished, + _ => PuzzleClearRunStatus::Playing, + } +} + +fn normalize_publication_status(value: &str) -> &str { + match value { + "Published" | "published" => "published", + _ => "draft", + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings.rs b/server-rs/crates/spacetime-client/src/module_bindings.rs index a62b5d82..b1edf48c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings.rs @@ -25,6 +25,7 @@ pub mod admin_work_visibility_list_procedure_result_type; pub mod admin_work_visibility_procedure_result_type; pub mod admin_work_visibility_snapshot_type; pub mod admin_work_visibility_update_input_type; +pub mod advance_puzzle_clear_next_level_procedure; pub mod advance_puzzle_next_level_procedure; pub mod ai_result_reference_input_type; pub mod ai_result_reference_kind_type; @@ -125,6 +126,7 @@ pub mod bark_battle_runtime_run_row_type; pub mod bark_battle_runtime_run_table; pub mod bark_battle_score_record_row_type; pub mod bark_battle_score_record_table; +pub mod bark_battle_work_delete_input_type; pub mod bark_battle_work_publish_input_type; pub mod bark_battle_work_stats_projection_row_type; pub mod bark_battle_work_stats_projection_table; @@ -212,6 +214,7 @@ pub mod compile_custom_world_published_profile_procedure; pub mod compile_jump_hop_draft_procedure; pub mod compile_match_3_d_draft_procedure; pub mod compile_puzzle_agent_draft_procedure; +pub mod compile_puzzle_clear_draft_procedure; pub mod compile_square_hole_draft_procedure; pub mod compile_visual_novel_work_profile_procedure; pub mod compile_wooden_fish_draft_procedure; @@ -234,6 +237,7 @@ pub mod create_jump_hop_agent_session_procedure; pub mod create_match_3_d_agent_session_procedure; pub mod create_profile_recharge_order_and_return_procedure; pub mod create_puzzle_agent_session_procedure; +pub mod create_puzzle_clear_agent_session_procedure; pub mod create_square_hole_agent_session_procedure; pub mod create_visual_novel_agent_session_procedure; pub mod create_wooden_fish_agent_session_procedure; @@ -325,14 +329,17 @@ pub mod database_migration_procedure_result_type; pub mod database_migration_revoke_operator_input_type; pub mod database_migration_table_stat_type; pub mod database_migration_warning_type; +pub mod delete_bark_battle_work_procedure; pub mod delete_big_fish_work_procedure; pub mod delete_custom_world_agent_session_procedure; pub mod delete_custom_world_profile_and_return_procedure; +pub mod delete_jump_hop_work_procedure; pub mod delete_match_3_d_work_procedure; pub mod delete_puzzle_work_procedure; pub mod delete_runtime_snapshot_and_return_procedure; pub mod delete_square_hole_work_procedure; pub mod delete_visual_novel_work_procedure; +pub mod delete_wooden_fish_work_procedure; pub mod drag_puzzle_piece_or_group_procedure; pub mod drop_square_hole_shape_procedure; pub mod ensure_analytics_date_dimension_for_date_reducer; @@ -380,6 +387,9 @@ pub mod get_profile_recharge_order_and_return_procedure; pub mod get_profile_referral_invite_center_procedure; pub mod get_profile_task_center_procedure; pub mod get_puzzle_agent_session_procedure; +pub mod get_puzzle_clear_agent_session_procedure; +pub mod get_puzzle_clear_runtime_run_procedure; +pub mod get_puzzle_clear_work_profile_procedure; pub mod get_puzzle_gallery_detail_procedure; pub mod get_puzzle_run_procedure; pub mod get_puzzle_work_detail_procedure; @@ -454,6 +464,7 @@ pub mod jump_hop_runtime_run_table; pub mod jump_hop_scoring_type; pub mod jump_hop_tile_asset_snapshot_type; pub mod jump_hop_tile_type_type; +pub mod jump_hop_work_delete_input_type; pub mod jump_hop_work_get_input_type; pub mod jump_hop_work_procedure_result_type; pub mod jump_hop_work_profile_row_type; @@ -473,6 +484,7 @@ pub mod list_match_3_d_works_procedure; pub mod list_platform_browse_history_procedure; pub mod list_profile_save_archives_procedure; pub mod list_profile_wallet_ledger_procedure; +pub mod list_puzzle_clear_works_procedure; pub mod list_puzzle_gallery_procedure; pub mod list_puzzle_works_procedure; pub mod list_square_hole_works_procedure; @@ -480,6 +492,7 @@ pub mod list_visual_novel_runtime_history_procedure; pub mod list_visual_novel_works_procedure; pub mod list_wooden_fish_works_procedure; pub mod mark_profile_recharge_order_paid_and_return_procedure; +pub mod mark_puzzle_clear_level_time_up_procedure; pub mod mark_puzzle_draft_generation_failed_procedure; pub mod match_3_d_agent_message_finalize_input_type; pub mod match_3_d_agent_message_row_type; @@ -587,6 +600,7 @@ pub mod publish_custom_world_profile_reducer; pub mod publish_custom_world_world_procedure; pub mod publish_jump_hop_work_procedure; pub mod publish_match_3_d_work_procedure; +pub mod publish_puzzle_clear_work_procedure; pub mod publish_puzzle_work_procedure; pub mod publish_square_hole_work_procedure; pub mod publish_visual_novel_work_procedure; @@ -613,6 +627,44 @@ pub mod puzzle_anchor_status_type; pub mod puzzle_audio_asset_type; pub mod puzzle_board_snapshot_type; pub mod puzzle_cell_position_type; +pub mod puzzle_clear_agent_session_create_input_type; +pub mod puzzle_clear_agent_session_get_input_type; +pub mod puzzle_clear_agent_session_procedure_result_type; +pub mod puzzle_clear_agent_session_row_type; +pub mod puzzle_clear_agent_session_snapshot_type; +pub mod puzzle_clear_agent_session_table; +pub mod puzzle_clear_board_cell_snapshot_type; +pub mod puzzle_clear_board_snapshot_type; +pub mod puzzle_clear_card_asset_snapshot_type; +pub mod puzzle_clear_draft_compile_input_type; +pub mod puzzle_clear_draft_snapshot_type; +pub mod puzzle_clear_event_row_type; +pub mod puzzle_clear_event_table; +pub mod puzzle_clear_gallery_card_view_row_type; +pub mod puzzle_clear_gallery_card_view_table; +pub mod puzzle_clear_gallery_view_row_type; +pub mod puzzle_clear_gallery_view_table; +pub mod puzzle_clear_image_asset_snapshot_type; +pub mod puzzle_clear_pattern_group_snapshot_type; +pub mod puzzle_clear_run_get_input_type; +pub mod puzzle_clear_run_next_level_input_type; +pub mod puzzle_clear_run_procedure_result_type; +pub mod puzzle_clear_run_retry_level_input_type; +pub mod puzzle_clear_run_start_input_type; +pub mod puzzle_clear_run_swap_input_type; +pub mod puzzle_clear_run_time_up_input_type; +pub mod puzzle_clear_runtime_run_row_type; +pub mod puzzle_clear_runtime_run_table; +pub mod puzzle_clear_runtime_snapshot_type; +pub mod puzzle_clear_work_get_input_type; +pub mod puzzle_clear_work_procedure_result_type; +pub mod puzzle_clear_work_profile_row_type; +pub mod puzzle_clear_work_profile_table; +pub mod puzzle_clear_work_publish_input_type; +pub mod puzzle_clear_work_snapshot_type; +pub mod puzzle_clear_work_update_input_type; +pub mod puzzle_clear_works_list_input_type; +pub mod puzzle_clear_works_procedure_result_type; pub mod puzzle_creator_intent_type; pub mod puzzle_draft_compile_failure_input_type; pub mod puzzle_draft_compile_input_type; @@ -733,6 +785,7 @@ pub mod restart_jump_hop_run_procedure; pub mod restart_match_3_d_run_procedure; pub mod restart_square_hole_run_procedure; pub mod resume_profile_save_archive_and_return_procedure; +pub mod retry_puzzle_clear_level_run_procedure; pub mod revoke_database_migration_operator_procedure; pub mod rpg_agent_draft_card_kind_type; pub mod rpg_agent_draft_card_status_type; @@ -903,6 +956,7 @@ pub mod start_bark_battle_run_procedure; pub mod start_big_fish_run_procedure; pub mod start_jump_hop_run_procedure; pub mod start_match_3_d_run_procedure; +pub mod start_puzzle_clear_runtime_run_procedure; pub mod start_puzzle_run_procedure; pub mod start_square_hole_run_procedure; pub mod start_visual_novel_run_procedure; @@ -931,6 +985,7 @@ pub mod submit_puzzle_agent_message_procedure; pub mod submit_puzzle_leaderboard_entry_procedure; pub mod submit_square_hole_agent_message_procedure; pub mod submit_visual_novel_agent_message_procedure; +pub mod swap_puzzle_clear_cards_procedure; pub mod swap_puzzle_pieces_procedure; pub mod tracking_daily_stat_table; pub mod tracking_daily_stat_type; @@ -949,6 +1004,7 @@ pub mod unpublish_custom_world_profile_reducer; pub mod update_bark_battle_draft_config_procedure; pub mod update_jump_hop_work_procedure; pub mod update_match_3_d_work_procedure; +pub mod update_puzzle_clear_work_procedure; pub mod update_puzzle_run_pause_procedure; pub mod update_puzzle_work_procedure; pub mod update_square_hole_work_procedure; @@ -1043,6 +1099,7 @@ pub mod wooden_fish_run_status_type; pub mod wooden_fish_runtime_run_row_type; pub mod wooden_fish_runtime_run_table; pub mod wooden_fish_word_counter_type; +pub mod wooden_fish_work_delete_input_type; pub mod wooden_fish_work_get_input_type; pub mod wooden_fish_work_procedure_result_type; pub mod wooden_fish_work_profile_row_type; @@ -1072,6 +1129,7 @@ pub use admin_work_visibility_list_procedure_result_type::AdminWorkVisibilityLis pub use admin_work_visibility_procedure_result_type::AdminWorkVisibilityProcedureResult; pub use admin_work_visibility_snapshot_type::AdminWorkVisibilitySnapshot; pub use admin_work_visibility_update_input_type::AdminWorkVisibilityUpdateInput; +pub use advance_puzzle_clear_next_level_procedure::advance_puzzle_clear_next_level; pub use advance_puzzle_next_level_procedure::advance_puzzle_next_level; pub use ai_result_reference_input_type::AiResultReferenceInput; pub use ai_result_reference_kind_type::AiResultReferenceKind; @@ -1172,6 +1230,7 @@ pub use bark_battle_runtime_run_row_type::BarkBattleRuntimeRunRow; pub use bark_battle_runtime_run_table::*; pub use bark_battle_score_record_row_type::BarkBattleScoreRecordRow; pub use bark_battle_score_record_table::*; +pub use bark_battle_work_delete_input_type::BarkBattleWorkDeleteInput; pub use bark_battle_work_publish_input_type::BarkBattleWorkPublishInput; pub use bark_battle_work_stats_projection_row_type::BarkBattleWorkStatsProjectionRow; pub use bark_battle_work_stats_projection_table::*; @@ -1259,6 +1318,7 @@ pub use compile_custom_world_published_profile_procedure::compile_custom_world_p pub use compile_jump_hop_draft_procedure::compile_jump_hop_draft; pub use compile_match_3_d_draft_procedure::compile_match_3_d_draft; pub use compile_puzzle_agent_draft_procedure::compile_puzzle_agent_draft; +pub use compile_puzzle_clear_draft_procedure::compile_puzzle_clear_draft; pub use compile_square_hole_draft_procedure::compile_square_hole_draft; pub use compile_visual_novel_work_profile_procedure::compile_visual_novel_work_profile; pub use compile_wooden_fish_draft_procedure::compile_wooden_fish_draft; @@ -1281,6 +1341,7 @@ pub use create_jump_hop_agent_session_procedure::create_jump_hop_agent_session; pub use create_match_3_d_agent_session_procedure::create_match_3_d_agent_session; pub use create_profile_recharge_order_and_return_procedure::create_profile_recharge_order_and_return; pub use create_puzzle_agent_session_procedure::create_puzzle_agent_session; +pub use create_puzzle_clear_agent_session_procedure::create_puzzle_clear_agent_session; pub use create_square_hole_agent_session_procedure::create_square_hole_agent_session; pub use create_visual_novel_agent_session_procedure::create_visual_novel_agent_session; pub use create_wooden_fish_agent_session_procedure::create_wooden_fish_agent_session; @@ -1372,14 +1433,17 @@ pub use database_migration_procedure_result_type::DatabaseMigrationProcedureResu pub use database_migration_revoke_operator_input_type::DatabaseMigrationRevokeOperatorInput; pub use database_migration_table_stat_type::DatabaseMigrationTableStat; pub use database_migration_warning_type::DatabaseMigrationWarning; +pub use delete_bark_battle_work_procedure::delete_bark_battle_work; pub use delete_big_fish_work_procedure::delete_big_fish_work; pub use delete_custom_world_agent_session_procedure::delete_custom_world_agent_session; pub use delete_custom_world_profile_and_return_procedure::delete_custom_world_profile_and_return; +pub use delete_jump_hop_work_procedure::delete_jump_hop_work; pub use delete_match_3_d_work_procedure::delete_match_3_d_work; pub use delete_puzzle_work_procedure::delete_puzzle_work; pub use delete_runtime_snapshot_and_return_procedure::delete_runtime_snapshot_and_return; pub use delete_square_hole_work_procedure::delete_square_hole_work; pub use delete_visual_novel_work_procedure::delete_visual_novel_work; +pub use delete_wooden_fish_work_procedure::delete_wooden_fish_work; pub use drag_puzzle_piece_or_group_procedure::drag_puzzle_piece_or_group; pub use drop_square_hole_shape_procedure::drop_square_hole_shape; pub use ensure_analytics_date_dimension_for_date_reducer::ensure_analytics_date_dimension_for_date; @@ -1427,6 +1491,9 @@ pub use get_profile_recharge_order_and_return_procedure::get_profile_recharge_or pub use get_profile_referral_invite_center_procedure::get_profile_referral_invite_center; pub use get_profile_task_center_procedure::get_profile_task_center; pub use get_puzzle_agent_session_procedure::get_puzzle_agent_session; +pub use get_puzzle_clear_agent_session_procedure::get_puzzle_clear_agent_session; +pub use get_puzzle_clear_runtime_run_procedure::get_puzzle_clear_runtime_run; +pub use get_puzzle_clear_work_profile_procedure::get_puzzle_clear_work_profile; pub use get_puzzle_gallery_detail_procedure::get_puzzle_gallery_detail; pub use get_puzzle_run_procedure::get_puzzle_run; pub use get_puzzle_work_detail_procedure::get_puzzle_work_detail; @@ -1501,6 +1568,7 @@ pub use jump_hop_runtime_run_table::*; pub use jump_hop_scoring_type::JumpHopScoring; pub use jump_hop_tile_asset_snapshot_type::JumpHopTileAssetSnapshot; pub use jump_hop_tile_type_type::JumpHopTileType; +pub use jump_hop_work_delete_input_type::JumpHopWorkDeleteInput; pub use jump_hop_work_get_input_type::JumpHopWorkGetInput; pub use jump_hop_work_procedure_result_type::JumpHopWorkProcedureResult; pub use jump_hop_work_profile_row_type::JumpHopWorkProfileRow; @@ -1520,6 +1588,7 @@ pub use list_match_3_d_works_procedure::list_match_3_d_works; pub use list_platform_browse_history_procedure::list_platform_browse_history; pub use list_profile_save_archives_procedure::list_profile_save_archives; pub use list_profile_wallet_ledger_procedure::list_profile_wallet_ledger; +pub use list_puzzle_clear_works_procedure::list_puzzle_clear_works; pub use list_puzzle_gallery_procedure::list_puzzle_gallery; pub use list_puzzle_works_procedure::list_puzzle_works; pub use list_square_hole_works_procedure::list_square_hole_works; @@ -1527,6 +1596,7 @@ pub use list_visual_novel_runtime_history_procedure::list_visual_novel_runtime_h pub use list_visual_novel_works_procedure::list_visual_novel_works; pub use list_wooden_fish_works_procedure::list_wooden_fish_works; pub use mark_profile_recharge_order_paid_and_return_procedure::mark_profile_recharge_order_paid_and_return; +pub use mark_puzzle_clear_level_time_up_procedure::mark_puzzle_clear_level_time_up; pub use mark_puzzle_draft_generation_failed_procedure::mark_puzzle_draft_generation_failed; pub use match_3_d_agent_message_finalize_input_type::Match3DAgentMessageFinalizeInput; pub use match_3_d_agent_message_row_type::Match3DAgentMessageRow; @@ -1634,6 +1704,7 @@ pub use publish_custom_world_profile_reducer::publish_custom_world_profile; pub use publish_custom_world_world_procedure::publish_custom_world_world; pub use publish_jump_hop_work_procedure::publish_jump_hop_work; pub use publish_match_3_d_work_procedure::publish_match_3_d_work; +pub use publish_puzzle_clear_work_procedure::publish_puzzle_clear_work; pub use publish_puzzle_work_procedure::publish_puzzle_work; pub use publish_square_hole_work_procedure::publish_square_hole_work; pub use publish_visual_novel_work_procedure::publish_visual_novel_work; @@ -1660,6 +1731,44 @@ pub use puzzle_anchor_status_type::PuzzleAnchorStatus; pub use puzzle_audio_asset_type::PuzzleAudioAsset; pub use puzzle_board_snapshot_type::PuzzleBoardSnapshot; pub use puzzle_cell_position_type::PuzzleCellPosition; +pub use puzzle_clear_agent_session_create_input_type::PuzzleClearAgentSessionCreateInput; +pub use puzzle_clear_agent_session_get_input_type::PuzzleClearAgentSessionGetInput; +pub use puzzle_clear_agent_session_procedure_result_type::PuzzleClearAgentSessionProcedureResult; +pub use puzzle_clear_agent_session_row_type::PuzzleClearAgentSessionRow; +pub use puzzle_clear_agent_session_snapshot_type::PuzzleClearAgentSessionSnapshot; +pub use puzzle_clear_agent_session_table::*; +pub use puzzle_clear_board_cell_snapshot_type::PuzzleClearBoardCellSnapshot; +pub use puzzle_clear_board_snapshot_type::PuzzleClearBoardSnapshot; +pub use puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; +pub use puzzle_clear_draft_compile_input_type::PuzzleClearDraftCompileInput; +pub use puzzle_clear_draft_snapshot_type::PuzzleClearDraftSnapshot; +pub use puzzle_clear_event_row_type::PuzzleClearEventRow; +pub use puzzle_clear_event_table::*; +pub use puzzle_clear_gallery_card_view_row_type::PuzzleClearGalleryCardViewRow; +pub use puzzle_clear_gallery_card_view_table::*; +pub use puzzle_clear_gallery_view_row_type::PuzzleClearGalleryViewRow; +pub use puzzle_clear_gallery_view_table::*; +pub use puzzle_clear_image_asset_snapshot_type::PuzzleClearImageAssetSnapshot; +pub use puzzle_clear_pattern_group_snapshot_type::PuzzleClearPatternGroupSnapshot; +pub use puzzle_clear_run_get_input_type::PuzzleClearRunGetInput; +pub use puzzle_clear_run_next_level_input_type::PuzzleClearRunNextLevelInput; +pub use puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; +pub use puzzle_clear_run_retry_level_input_type::PuzzleClearRunRetryLevelInput; +pub use puzzle_clear_run_start_input_type::PuzzleClearRunStartInput; +pub use puzzle_clear_run_swap_input_type::PuzzleClearRunSwapInput; +pub use puzzle_clear_run_time_up_input_type::PuzzleClearRunTimeUpInput; +pub use puzzle_clear_runtime_run_row_type::PuzzleClearRuntimeRunRow; +pub use puzzle_clear_runtime_run_table::*; +pub use puzzle_clear_runtime_snapshot_type::PuzzleClearRuntimeSnapshot; +pub use puzzle_clear_work_get_input_type::PuzzleClearWorkGetInput; +pub use puzzle_clear_work_procedure_result_type::PuzzleClearWorkProcedureResult; +pub use puzzle_clear_work_profile_row_type::PuzzleClearWorkProfileRow; +pub use puzzle_clear_work_profile_table::*; +pub use puzzle_clear_work_publish_input_type::PuzzleClearWorkPublishInput; +pub use puzzle_clear_work_snapshot_type::PuzzleClearWorkSnapshot; +pub use puzzle_clear_work_update_input_type::PuzzleClearWorkUpdateInput; +pub use puzzle_clear_works_list_input_type::PuzzleClearWorksListInput; +pub use puzzle_clear_works_procedure_result_type::PuzzleClearWorksProcedureResult; pub use puzzle_creator_intent_type::PuzzleCreatorIntent; pub use puzzle_draft_compile_failure_input_type::PuzzleDraftCompileFailureInput; pub use puzzle_draft_compile_input_type::PuzzleDraftCompileInput; @@ -1780,6 +1889,7 @@ pub use restart_jump_hop_run_procedure::restart_jump_hop_run; pub use restart_match_3_d_run_procedure::restart_match_3_d_run; pub use restart_square_hole_run_procedure::restart_square_hole_run; pub use resume_profile_save_archive_and_return_procedure::resume_profile_save_archive_and_return; +pub use retry_puzzle_clear_level_run_procedure::retry_puzzle_clear_level_run; pub use revoke_database_migration_operator_procedure::revoke_database_migration_operator; pub use rpg_agent_draft_card_kind_type::RpgAgentDraftCardKind; pub use rpg_agent_draft_card_status_type::RpgAgentDraftCardStatus; @@ -1950,6 +2060,7 @@ pub use start_bark_battle_run_procedure::start_bark_battle_run; pub use start_big_fish_run_procedure::start_big_fish_run; pub use start_jump_hop_run_procedure::start_jump_hop_run; pub use start_match_3_d_run_procedure::start_match_3_d_run; +pub use start_puzzle_clear_runtime_run_procedure::start_puzzle_clear_runtime_run; pub use start_puzzle_run_procedure::start_puzzle_run; pub use start_square_hole_run_procedure::start_square_hole_run; pub use start_visual_novel_run_procedure::start_visual_novel_run; @@ -1978,6 +2089,7 @@ pub use submit_puzzle_agent_message_procedure::submit_puzzle_agent_message; pub use submit_puzzle_leaderboard_entry_procedure::submit_puzzle_leaderboard_entry; pub use submit_square_hole_agent_message_procedure::submit_square_hole_agent_message; pub use submit_visual_novel_agent_message_procedure::submit_visual_novel_agent_message; +pub use swap_puzzle_clear_cards_procedure::swap_puzzle_clear_cards; pub use swap_puzzle_pieces_procedure::swap_puzzle_pieces; pub use tracking_daily_stat_table::*; pub use tracking_daily_stat_type::TrackingDailyStat; @@ -1996,6 +2108,7 @@ pub use unpublish_custom_world_profile_reducer::unpublish_custom_world_profile; pub use update_bark_battle_draft_config_procedure::update_bark_battle_draft_config; pub use update_jump_hop_work_procedure::update_jump_hop_work; pub use update_match_3_d_work_procedure::update_match_3_d_work; +pub use update_puzzle_clear_work_procedure::update_puzzle_clear_work; pub use update_puzzle_run_pause_procedure::update_puzzle_run_pause; pub use update_puzzle_work_procedure::update_puzzle_work; pub use update_square_hole_work_procedure::update_square_hole_work; @@ -2090,6 +2203,7 @@ pub use wooden_fish_run_status_type::WoodenFishRunStatus; pub use wooden_fish_runtime_run_row_type::WoodenFishRuntimeRunRow; pub use wooden_fish_runtime_run_table::*; pub use wooden_fish_word_counter_type::WoodenFishWordCounter; +pub use wooden_fish_work_delete_input_type::WoodenFishWorkDeleteInput; pub use wooden_fish_work_get_input_type::WoodenFishWorkGetInput; pub use wooden_fish_work_procedure_result_type::WoodenFishWorkProcedureResult; pub use wooden_fish_work_profile_row_type::WoodenFishWorkProfileRow; @@ -2447,6 +2561,12 @@ pub struct DbUpdate { public_work_play_daily_stat: __sdk::TableUpdate, puzzle_agent_message: __sdk::TableUpdate, puzzle_agent_session: __sdk::TableUpdate, + puzzle_clear_agent_session: __sdk::TableUpdate, + puzzle_clear_event: __sdk::TableUpdate, + puzzle_clear_gallery_card_view: __sdk::TableUpdate, + puzzle_clear_gallery_view: __sdk::TableUpdate, + puzzle_clear_runtime_run: __sdk::TableUpdate, + puzzle_clear_work_profile: __sdk::TableUpdate, puzzle_event: __sdk::TableUpdate, puzzle_gallery_card_view: __sdk::TableUpdate, puzzle_gallery_view: __sdk::TableUpdate, @@ -2726,6 +2846,26 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { "puzzle_agent_session" => db_update.puzzle_agent_session.append( puzzle_agent_session_table::parse_table_update(table_update)?, ), + "puzzle_clear_agent_session" => db_update.puzzle_clear_agent_session.append( + puzzle_clear_agent_session_table::parse_table_update(table_update)?, + ), + "puzzle_clear_event" => db_update + .puzzle_clear_event + .append(puzzle_clear_event_table::parse_table_update(table_update)?), + "puzzle_clear_gallery_card_view" => { + db_update.puzzle_clear_gallery_card_view.append( + puzzle_clear_gallery_card_view_table::parse_table_update(table_update)?, + ) + } + "puzzle_clear_gallery_view" => db_update.puzzle_clear_gallery_view.append( + puzzle_clear_gallery_view_table::parse_table_update(table_update)?, + ), + "puzzle_clear_runtime_run" => db_update.puzzle_clear_runtime_run.append( + puzzle_clear_runtime_run_table::parse_table_update(table_update)?, + ), + "puzzle_clear_work_profile" => db_update.puzzle_clear_work_profile.append( + puzzle_clear_work_profile_table::parse_table_update(table_update)?, + ), "puzzle_event" => db_update .puzzle_event .append(puzzle_event_table::parse_table_update(table_update)?), @@ -3225,6 +3365,30 @@ impl __sdk::DbUpdate for DbUpdate { &self.puzzle_agent_session, ) .with_updates_by_pk(|row| &row.session_id); + diff.puzzle_clear_agent_session = cache + .apply_diff_to_table::( + "puzzle_clear_agent_session", + &self.puzzle_clear_agent_session, + ) + .with_updates_by_pk(|row| &row.session_id); + diff.puzzle_clear_event = cache + .apply_diff_to_table::( + "puzzle_clear_event", + &self.puzzle_clear_event, + ) + .with_updates_by_pk(|row| &row.event_id); + diff.puzzle_clear_runtime_run = cache + .apply_diff_to_table::( + "puzzle_clear_runtime_run", + &self.puzzle_clear_runtime_run, + ) + .with_updates_by_pk(|row| &row.run_id); + diff.puzzle_clear_work_profile = cache + .apply_diff_to_table::( + "puzzle_clear_work_profile", + &self.puzzle_clear_work_profile, + ) + .with_updates_by_pk(|row| &row.profile_id); diff.puzzle_event = self.puzzle_event.into_event_diff(); diff.puzzle_leaderboard_entry = cache .apply_diff_to_table::( @@ -3390,6 +3554,15 @@ impl __sdk::DbUpdate for DbUpdate { "public_work_gallery_entry", &self.public_work_gallery_entry, ); + diff.puzzle_clear_gallery_card_view = cache + .apply_diff_to_table::( + "puzzle_clear_gallery_card_view", + &self.puzzle_clear_gallery_card_view, + ); + diff.puzzle_clear_gallery_view = cache.apply_diff_to_table::( + "puzzle_clear_gallery_view", + &self.puzzle_clear_gallery_view, + ); diff.puzzle_gallery_card_view = cache.apply_diff_to_table::( "puzzle_gallery_card_view", &self.puzzle_gallery_card_view, @@ -3647,6 +3820,24 @@ impl __sdk::DbUpdate for DbUpdate { "puzzle_agent_session" => db_update .puzzle_agent_session .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "puzzle_clear_agent_session" => db_update + .puzzle_clear_agent_session + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "puzzle_clear_event" => db_update + .puzzle_clear_event + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "puzzle_clear_gallery_card_view" => db_update + .puzzle_clear_gallery_card_view + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "puzzle_clear_gallery_view" => db_update + .puzzle_clear_gallery_view + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "puzzle_clear_runtime_run" => db_update + .puzzle_clear_runtime_run + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "puzzle_clear_work_profile" => db_update + .puzzle_clear_work_profile + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "puzzle_event" => db_update .puzzle_event .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), @@ -3993,6 +4184,24 @@ impl __sdk::DbUpdate for DbUpdate { "puzzle_agent_session" => db_update .puzzle_agent_session .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "puzzle_clear_agent_session" => db_update + .puzzle_clear_agent_session + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "puzzle_clear_event" => db_update + .puzzle_clear_event + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "puzzle_clear_gallery_card_view" => db_update + .puzzle_clear_gallery_card_view + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "puzzle_clear_gallery_view" => db_update + .puzzle_clear_gallery_view + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "puzzle_clear_runtime_run" => db_update + .puzzle_clear_runtime_run + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "puzzle_clear_work_profile" => db_update + .puzzle_clear_work_profile + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "puzzle_event" => db_update .puzzle_event .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), @@ -4193,6 +4402,12 @@ pub struct AppliedDiff<'r> { public_work_play_daily_stat: __sdk::TableAppliedDiff<'r, PublicWorkPlayDailyStat>, puzzle_agent_message: __sdk::TableAppliedDiff<'r, PuzzleAgentMessageRow>, puzzle_agent_session: __sdk::TableAppliedDiff<'r, PuzzleAgentSessionRow>, + puzzle_clear_agent_session: __sdk::TableAppliedDiff<'r, PuzzleClearAgentSessionRow>, + puzzle_clear_event: __sdk::TableAppliedDiff<'r, PuzzleClearEventRow>, + puzzle_clear_gallery_card_view: __sdk::TableAppliedDiff<'r, PuzzleClearGalleryCardViewRow>, + puzzle_clear_gallery_view: __sdk::TableAppliedDiff<'r, PuzzleClearGalleryViewRow>, + puzzle_clear_runtime_run: __sdk::TableAppliedDiff<'r, PuzzleClearRuntimeRunRow>, + puzzle_clear_work_profile: __sdk::TableAppliedDiff<'r, PuzzleClearWorkProfileRow>, puzzle_event: __sdk::TableAppliedDiff<'r, PuzzleEvent>, puzzle_gallery_card_view: __sdk::TableAppliedDiff<'r, PuzzleGalleryCardViewRow>, puzzle_gallery_view: __sdk::TableAppliedDiff<'r, PuzzleWorkProfile>, @@ -4606,6 +4821,36 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { &self.puzzle_agent_session, event, ); + callbacks.invoke_table_row_callbacks::( + "puzzle_clear_agent_session", + &self.puzzle_clear_agent_session, + event, + ); + callbacks.invoke_table_row_callbacks::( + "puzzle_clear_event", + &self.puzzle_clear_event, + event, + ); + callbacks.invoke_table_row_callbacks::( + "puzzle_clear_gallery_card_view", + &self.puzzle_clear_gallery_card_view, + event, + ); + callbacks.invoke_table_row_callbacks::( + "puzzle_clear_gallery_view", + &self.puzzle_clear_gallery_view, + event, + ); + callbacks.invoke_table_row_callbacks::( + "puzzle_clear_runtime_run", + &self.puzzle_clear_runtime_run, + event, + ); + callbacks.invoke_table_row_callbacks::( + "puzzle_clear_work_profile", + &self.puzzle_clear_work_profile, + event, + ); callbacks.invoke_table_row_callbacks::( "puzzle_event", &self.puzzle_event, @@ -5033,19 +5278,19 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle { /// either a [`DbConnection`] or an [`EventContext`] and operate on either. pub trait RemoteDbContext: __sdk::DbContext< - DbView = RemoteTables, - Reducers = RemoteReducers, - SubscriptionBuilder = __sdk::SubscriptionBuilder, -> + DbView = RemoteTables, + Reducers = RemoteReducers, + SubscriptionBuilder = __sdk::SubscriptionBuilder, + > { } impl< - Ctx: __sdk::DbContext< + Ctx: __sdk::DbContext< DbView = RemoteTables, Reducers = RemoteReducers, SubscriptionBuilder = __sdk::SubscriptionBuilder, >, - > RemoteDbContext for Ctx +> RemoteDbContext for Ctx { } @@ -5513,6 +5758,12 @@ impl __sdk::SpacetimeModule for RemoteModule { public_work_play_daily_stat_table::register_table(client_cache); puzzle_agent_message_table::register_table(client_cache); puzzle_agent_session_table::register_table(client_cache); + puzzle_clear_agent_session_table::register_table(client_cache); + puzzle_clear_event_table::register_table(client_cache); + puzzle_clear_gallery_card_view_table::register_table(client_cache); + puzzle_clear_gallery_view_table::register_table(client_cache); + puzzle_clear_runtime_run_table::register_table(client_cache); + puzzle_clear_work_profile_table::register_table(client_cache); puzzle_event_table::register_table(client_cache); puzzle_gallery_card_view_table::register_table(client_cache); puzzle_gallery_view_table::register_table(client_cache); @@ -5626,6 +5877,12 @@ impl __sdk::SpacetimeModule for RemoteModule { "public_work_play_daily_stat", "puzzle_agent_message", "puzzle_agent_session", + "puzzle_clear_agent_session", + "puzzle_clear_event", + "puzzle_clear_gallery_card_view", + "puzzle_clear_gallery_view", + "puzzle_clear_runtime_run", + "puzzle_clear_work_profile", "puzzle_event", "puzzle_gallery_card_view", "puzzle_gallery_view", diff --git a/server-rs/crates/spacetime-client/src/module_bindings/accept_quest_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/accept_quest_reducer.rs index dfebf903..61e6b9c5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/accept_quest_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/accept_quest_reducer.rs @@ -47,9 +47,11 @@ pub trait accept_quest { &self, input: QuestRecordInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl accept_quest for super::RemoteReducers { &self, input: QuestRecordInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(AcceptQuestArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/acknowledge_quest_completion_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/acknowledge_quest_completion_reducer.rs index 6ae2fd10..b1419fb7 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/acknowledge_quest_completion_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/acknowledge_quest_completion_reducer.rs @@ -47,9 +47,11 @@ pub trait acknowledge_quest_completion { &self, input: QuestCompletionAckInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl acknowledge_quest_completion for super::RemoteReducers { &self, input: QuestCompletionAckInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(AcknowledgeQuestCompletionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs index bbdaab4f..9865ace5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_disable_profile_redeem_code { input: RuntimeProfileRedeemCodeAdminDisableInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_disable_profile_redeem_code for super::RemoteProcedures { input: RuntimeProfileRedeemCodeAdminDisableInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_task_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_task_config_procedure.rs index c968f950..0417bd2e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_task_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_task_config_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_disable_profile_task_config { input: RuntimeProfileTaskConfigAdminDisableInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_disable_profile_task_config for super::RemoteProcedures { input: RuntimeProfileTaskConfigAdminDisableInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_invite_codes_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_invite_codes_procedure.rs index cdfa27d9..96d2350f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_invite_codes_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_invite_codes_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_list_profile_invite_codes { input: RuntimeProfileInviteCodeAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_list_profile_invite_codes for super::RemoteProcedures { input: RuntimeProfileInviteCodeAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileInviteCodeAdminListProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_recharge_products_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_recharge_products_procedure.rs index e84d4ec6..a1deed88 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_recharge_products_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_recharge_products_procedure.rs @@ -34,10 +34,10 @@ pub trait admin_list_profile_recharge_products { input: RuntimeProfileRechargeProductAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl admin_list_profile_recharge_products for super::RemoteProcedures { input: RuntimeProfileRechargeProductAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp.invoke_procedure_with_callback::<_, RuntimeProfileRechargeProductAdminListProcedureResult>( "admin_list_profile_recharge_products", diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_redeem_codes_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_redeem_codes_procedure.rs index 2c9b9dd7..c7d6a78e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_redeem_codes_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_redeem_codes_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_list_profile_redeem_codes { input: RuntimeProfileRedeemCodeAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_list_profile_redeem_codes for super::RemoteProcedures { input: RuntimeProfileRedeemCodeAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminListProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_task_configs_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_task_configs_procedure.rs index 88ca28d5..a152116d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_task_configs_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_profile_task_configs_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_list_profile_task_configs { input: RuntimeProfileTaskConfigAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_list_profile_task_configs for super::RemoteProcedures { input: RuntimeProfileTaskConfigAdminListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminListProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_work_visibility_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_work_visibility_procedure.rs index 72028b5e..df222e41 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_list_work_visibility_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_list_work_visibility_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_list_work_visibility { input: AdminWorkVisibilityListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_list_work_visibility for super::RemoteProcedures { input: AdminWorkVisibilityListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AdminWorkVisibilityListProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_update_work_visibility_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_update_work_visibility_procedure.rs index bbc85a89..4a88c084 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_update_work_visibility_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_update_work_visibility_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_update_work_visibility { input: AdminWorkVisibilityUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_update_work_visibility for super::RemoteProcedures { input: AdminWorkVisibilityUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AdminWorkVisibilityProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_invite_code_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_invite_code_procedure.rs index 3601be97..2411092d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_invite_code_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_invite_code_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_upsert_profile_invite_code { input: RuntimeProfileInviteCodeAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_upsert_profile_invite_code for super::RemoteProcedures { input: RuntimeProfileInviteCodeAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileInviteCodeAdminProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_recharge_product_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_recharge_product_procedure.rs index e3f42278..83941b83 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_recharge_product_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_recharge_product_procedure.rs @@ -34,10 +34,10 @@ pub trait admin_upsert_profile_recharge_product { input: RuntimeProfileRechargeProductAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl admin_upsert_profile_recharge_product for super::RemoteProcedures { input: RuntimeProfileRechargeProductAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRechargeProductAdminProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs index 7e918220..9c7ae92f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_upsert_profile_redeem_code { input: RuntimeProfileRedeemCodeAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_upsert_profile_redeem_code for super::RemoteProcedures { input: RuntimeProfileRedeemCodeAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_task_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_task_config_procedure.rs index a3d3e11a..b441a808 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_task_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_task_config_procedure.rs @@ -31,10 +31,10 @@ pub trait admin_upsert_profile_task_config { input: RuntimeProfileTaskConfigAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl admin_upsert_profile_task_config for super::RemoteProcedures { input: RuntimeProfileTaskConfigAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_clear_next_level_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_clear_next_level_procedure.rs new file mode 100644 index 00000000..a75b35e4 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_clear_next_level_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_run_next_level_input_type::PuzzleClearRunNextLevelInput; +use super::puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct AdvancePuzzleClearNextLevelArgs { + pub input: PuzzleClearRunNextLevelInput, +} + +impl __sdk::InModule for AdvancePuzzleClearNextLevelArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `advance_puzzle_clear_next_level`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait advance_puzzle_clear_next_level { + fn advance_puzzle_clear_next_level(&self, input: PuzzleClearRunNextLevelInput) { + self.advance_puzzle_clear_next_level_then(input, |_, _| {}); + } + + fn advance_puzzle_clear_next_level_then( + &self, + input: PuzzleClearRunNextLevelInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl advance_puzzle_clear_next_level for super::RemoteProcedures { + fn advance_puzzle_clear_next_level_then( + &self, + input: PuzzleClearRunNextLevelInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearRunProcedureResult>( + "advance_puzzle_clear_next_level", + AdvancePuzzleClearNextLevelArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs index 6d3e9f79..7cb4f8f4 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs @@ -31,10 +31,10 @@ pub trait advance_puzzle_next_level { input: PuzzleRunNextLevelInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl advance_puzzle_next_level for super::RemoteProcedures { input: PuzzleRunNextLevelInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs index 11323392..191e2ea7 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait append_ai_text_chunk_and_return { input: AiTextChunkAppendInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl append_ai_text_chunk_and_return for super::RemoteProcedures { input: AiTextChunkAppendInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/append_visual_novel_runtime_history_entry_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/append_visual_novel_runtime_history_entry_procedure.rs index 4686ba5c..ad1099d0 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/append_visual_novel_runtime_history_entry_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/append_visual_novel_runtime_history_entry_procedure.rs @@ -34,10 +34,10 @@ pub trait append_visual_novel_runtime_history_entry { input: VisualNovelRuntimeHistoryAppendInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl append_visual_novel_runtime_history_entry for super::RemoteProcedures { input: VisualNovelRuntimeHistoryAppendInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelHistoryProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs index bba4c841..4a949906 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs @@ -34,10 +34,10 @@ pub trait apply_chapter_progression_ledger_entry_and_return { input: ChapterProgressionLedgerInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl apply_chapter_progression_ledger_entry_and_return for super::RemoteProcedur input: ChapterProgressionLedgerInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, ChapterProgressionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_reducer.rs index 98f821ef..44596083 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_reducer.rs @@ -50,9 +50,11 @@ pub trait apply_chapter_progression_ledger_entry { &self, input: ChapterProgressionLedgerInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl apply_chapter_progression_ledger_entry for super::RemoteReducers { &self, input: ChapterProgressionLedgerInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp.invoke_reducer_with_callback( ApplyChapterProgressionLedgerEntryArgs { input }, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/apply_inventory_mutation_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/apply_inventory_mutation_reducer.rs index 91f7d2c0..d9b4240d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/apply_inventory_mutation_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/apply_inventory_mutation_reducer.rs @@ -47,9 +47,11 @@ pub trait apply_inventory_mutation { &self, input: InventoryMutationInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl apply_inventory_mutation for super::RemoteReducers { &self, input: InventoryMutationInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ApplyInventoryMutationArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/apply_quest_signal_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/apply_quest_signal_reducer.rs index afb452b5..6b4c310b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/apply_quest_signal_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/apply_quest_signal_reducer.rs @@ -47,9 +47,11 @@ pub trait apply_quest_signal { &self, input: QuestSignalApplyInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl apply_quest_signal for super::RemoteReducers { &self, input: QuestSignalApplyInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ApplyQuestSignalArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs index 2f3edbe2..94d41850 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait attach_ai_result_reference_and_return { input: AiResultReferenceInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl attach_ai_result_reference_and_return for super::RemoteProcedures { input: AiResultReferenceInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs index b5885022..ac77f7e8 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs @@ -34,10 +34,10 @@ pub trait authorize_database_migration_operator { input: DatabaseMigrationAuthorizeOperatorInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl authorize_database_migration_operator for super::RemoteProcedures { input: DatabaseMigrationAuthorizeOperatorInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationOperatorProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/bark_battle_work_delete_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/bark_battle_work_delete_input_type.rs new file mode 100644 index 00000000..9250b584 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/bark_battle_work_delete_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct BarkBattleWorkDeleteInput { + pub work_id: String, + pub owner_user_id: String, +} + +impl __sdk::InModule for BarkBattleWorkDeleteInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs index eef3de0f..304b2e0c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait begin_story_session_and_return { input: StorySessionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl begin_story_session_and_return for super::RemoteProcedures { input: StorySessionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, StorySessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_reducer.rs index 6a082f41..22bc4add 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_reducer.rs @@ -47,9 +47,11 @@ pub trait begin_story_session { &self, input: StorySessionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl begin_story_session for super::RemoteReducers { &self, input: StorySessionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(BeginStorySessionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs index b709d5c2..78c80aee 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait bind_asset_object_to_entity_and_return { input: AssetEntityBindingInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl bind_asset_object_to_entity_and_return for super::RemoteProcedures { input: AssetEntityBindingInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AssetEntityBindingProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_reducer.rs index b20bc5b2..caf48b26 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_reducer.rs @@ -47,9 +47,11 @@ pub trait bind_asset_object_to_entity { &self, input: AssetEntityBindingInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl bind_asset_object_to_entity for super::RemoteReducers { &self, input: AssetEntityBindingInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(BindAssetObjectToEntityArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs index b239e060..0c5dc3eb 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait cancel_ai_task_and_return { input: AiTaskCancelInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl cancel_ai_task_and_return for super::RemoteProcedures { input: AiTaskCancelInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/checkpoint_wooden_fish_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/checkpoint_wooden_fish_run_procedure.rs index 51aa864f..5bd5a45c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/checkpoint_wooden_fish_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/checkpoint_wooden_fish_run_procedure.rs @@ -31,10 +31,10 @@ pub trait checkpoint_wooden_fish_run { input: WoodenFishRunCheckpointInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl checkpoint_wooden_fish_run for super::RemoteProcedures { input: WoodenFishRunCheckpointInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/claim_profile_task_reward_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/claim_profile_task_reward_and_return_procedure.rs index 5a386f3c..ea501070 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/claim_profile_task_reward_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/claim_profile_task_reward_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait claim_profile_task_reward_and_return { input: RuntimeProfileTaskClaimInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl claim_profile_task_reward_and_return for super::RemoteProcedures { input: RuntimeProfileTaskClaimInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileTaskClaimProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/claim_puzzle_work_point_incentive_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/claim_puzzle_work_point_incentive_procedure.rs index f7ac8d75..1d787222 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/claim_puzzle_work_point_incentive_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/claim_puzzle_work_point_incentive_procedure.rs @@ -31,10 +31,10 @@ pub trait claim_puzzle_work_point_incentive { input: PuzzleWorkPointIncentiveClaimInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl claim_puzzle_work_point_incentive for super::RemoteProcedures { input: PuzzleWorkPointIncentiveClaimInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/clear_database_migration_import_chunks_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/clear_database_migration_import_chunks_procedure.rs index 51146e99..d05fcdf2 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/clear_database_migration_import_chunks_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/clear_database_migration_import_chunks_procedure.rs @@ -34,10 +34,10 @@ pub trait clear_database_migration_import_chunks { input: DatabaseMigrationImportChunksClearInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl clear_database_migration_import_chunks for super::RemoteProcedures { input: DatabaseMigrationImportChunksClearInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs index 2e623845..c8ba8d49 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait clear_platform_browse_history_and_return { input: RuntimeBrowseHistoryClearInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl clear_platform_browse_history_and_return for super::RemoteProcedures { input: RuntimeBrowseHistoryClearInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeBrowseHistoryProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/click_match_3_d_item_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/click_match_3_d_item_procedure.rs index 6070a62d..278845ba 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/click_match_3_d_item_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/click_match_3_d_item_procedure.rs @@ -31,10 +31,10 @@ pub trait click_match_3_d_item { input: Match3DRunClickInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl click_match_3_d_item for super::RemoteProcedures { input: Match3DRunClickInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DClickItemProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs index 6eea6571..bafe95a6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_big_fish_draft { input: BigFishDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_big_fish_draft for super::RemoteProcedures { input: BigFishDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs index aefbe602..67706fab 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs @@ -34,10 +34,10 @@ pub trait compile_custom_world_published_profile { input: CustomWorldPublishedProfileCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl compile_custom_world_published_profile for super::RemoteProcedures { input: CustomWorldPublishedProfileCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldPublishedProfileCompileResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_jump_hop_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_jump_hop_draft_procedure.rs index fe89daba..f0479afa 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_jump_hop_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_jump_hop_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_jump_hop_draft { input: JumpHopDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_jump_hop_draft for super::RemoteProcedures { input: JumpHopDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_match_3_d_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_match_3_d_draft_procedure.rs index d6db0787..1a7a97ed 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_match_3_d_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_match_3_d_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_match_3_d_draft { input: Match3DDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_match_3_d_draft for super::RemoteProcedures { input: Match3DDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs index 7badcdae..177c0c40 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_puzzle_agent_draft { input: PuzzleDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_puzzle_agent_draft for super::RemoteProcedures { input: PuzzleDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_clear_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_clear_draft_procedure.rs new file mode 100644 index 00000000..79726bb5 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_clear_draft_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_agent_session_procedure_result_type::PuzzleClearAgentSessionProcedureResult; +use super::puzzle_clear_draft_compile_input_type::PuzzleClearDraftCompileInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct CompilePuzzleClearDraftArgs { + pub input: PuzzleClearDraftCompileInput, +} + +impl __sdk::InModule for CompilePuzzleClearDraftArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `compile_puzzle_clear_draft`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait compile_puzzle_clear_draft { + fn compile_puzzle_clear_draft(&self, input: PuzzleClearDraftCompileInput) { + self.compile_puzzle_clear_draft_then(input, |_, _| {}); + } + + fn compile_puzzle_clear_draft_then( + &self, + input: PuzzleClearDraftCompileInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl compile_puzzle_clear_draft for super::RemoteProcedures { + fn compile_puzzle_clear_draft_then( + &self, + input: PuzzleClearDraftCompileInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearAgentSessionProcedureResult>( + "compile_puzzle_clear_draft", + CompilePuzzleClearDraftArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_square_hole_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_square_hole_draft_procedure.rs index ee6766b0..0b715fc4 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_square_hole_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_square_hole_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_square_hole_draft { input: SquareHoleDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_square_hole_draft for super::RemoteProcedures { input: SquareHoleDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_visual_novel_work_profile_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_visual_novel_work_profile_procedure.rs index 457f9809..e6d5f25b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_visual_novel_work_profile_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_visual_novel_work_profile_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_visual_novel_work_profile { input: VisualNovelWorkCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_visual_novel_work_profile for super::RemoteProcedures { input: VisualNovelWorkCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/compile_wooden_fish_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/compile_wooden_fish_draft_procedure.rs index 99e98ca8..61c7b013 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/compile_wooden_fish_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/compile_wooden_fish_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait compile_wooden_fish_draft { input: WoodenFishDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl compile_wooden_fish_draft for super::RemoteProcedures { input: WoodenFishDraftCompileInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs index 51375935..e59ab8f0 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait complete_ai_stage_and_return { input: AiStageCompletionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl complete_ai_stage_and_return for super::RemoteProcedures { input: AiStageCompletionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs index 040af639..ca7eab9f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait complete_ai_task_and_return { input: AiTaskFinishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl complete_ai_task_and_return for super::RemoteProcedures { input: AiTaskFinishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs index 0b4f26b2..cc65f744 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait confirm_asset_object_and_return { input: AssetObjectUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl confirm_asset_object_and_return for super::RemoteProcedures { input: AssetObjectUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AssetObjectProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_reducer.rs index 183c2efa..f5edb63a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_reducer.rs @@ -47,9 +47,11 @@ pub trait confirm_asset_object { &self, input: AssetObjectUpsertInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl confirm_asset_object for super::RemoteReducers { &self, input: AssetObjectUpsertInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ConfirmAssetObjectArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs index 3d3e47a2..11394b66 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait consume_profile_wallet_points_and_return { input: RuntimeProfileWalletAdjustmentInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl consume_profile_wallet_points_and_return for super::RemoteProcedures { input: RuntimeProfileWalletAdjustmentInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileWalletAdjustmentProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs index 0d58ec1b..1c2b51d8 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait continue_story_and_return { input: StoryContinueInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl continue_story_and_return for super::RemoteProcedures { input: StoryContinueInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, StorySessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/continue_story_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/continue_story_reducer.rs index 4117cfae..fb35bd1f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/continue_story_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/continue_story_reducer.rs @@ -47,9 +47,11 @@ pub trait continue_story { &self, input: StoryContinueInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl continue_story for super::RemoteReducers { &self, input: StoryContinueInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ContinueStoryArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs index 20d8ceee..a2f40fd0 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait create_ai_task_and_return { input: AiTaskCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_ai_task_and_return for super::RemoteProcedures { input: AiTaskCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_reducer.rs index 213f28e5..b87207f0 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_reducer.rs @@ -47,9 +47,11 @@ pub trait create_ai_task { &self, input: AiTaskCreateInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl create_ai_task for super::RemoteReducers { &self, input: AiTaskCreateInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(CreateAiTaskArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_bark_battle_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_bark_battle_draft_procedure.rs index dbc6f317..87e104b6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_bark_battle_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_bark_battle_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait create_bark_battle_draft { input: BarkBattleDraftCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_bark_battle_draft for super::RemoteProcedures { input: BarkBattleDraftCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs index ef11107a..c028ba4e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait create_battle_state_and_return { input: BattleStateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_battle_state_and_return for super::RemoteProcedures { input: BattleStateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BattleStateProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_reducer.rs index 1072f8a5..7f34eab2 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_reducer.rs @@ -47,9 +47,11 @@ pub trait create_battle_state { &self, input: BattleStateInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl create_battle_state for super::RemoteReducers { &self, input: BattleStateInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(CreateBattleStateArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs index bc67436e..6e48521a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_big_fish_session { input: BigFishSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_big_fish_session for super::RemoteProcedures { input: BigFishSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs index 6a08bcc7..96a21162 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_custom_world_agent_session { input: CustomWorldAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_custom_world_agent_session for super::RemoteProcedures { input: CustomWorldAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_jump_hop_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_jump_hop_agent_session_procedure.rs index e4eeb904..6bfce7c5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_jump_hop_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_jump_hop_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_jump_hop_agent_session { input: JumpHopAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_jump_hop_agent_session for super::RemoteProcedures { input: JumpHopAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_match_3_d_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_match_3_d_agent_session_procedure.rs index c482c9c6..717ef728 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_match_3_d_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_match_3_d_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_match_3_d_agent_session { input: Match3DAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_match_3_d_agent_session for super::RemoteProcedures { input: Match3DAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs index 893fbdf6..1c53f6ae 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs @@ -34,10 +34,10 @@ pub trait create_profile_recharge_order_and_return { input: RuntimeProfileRechargeOrderCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl create_profile_recharge_order_and_return for super::RemoteProcedures { input: RuntimeProfileRechargeOrderCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRechargeCenterProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs index 9460692b..62b77081 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_puzzle_agent_session { input: PuzzleAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_puzzle_agent_session for super::RemoteProcedures { input: PuzzleAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_clear_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_clear_agent_session_procedure.rs new file mode 100644 index 00000000..1059b5c6 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_clear_agent_session_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_agent_session_create_input_type::PuzzleClearAgentSessionCreateInput; +use super::puzzle_clear_agent_session_procedure_result_type::PuzzleClearAgentSessionProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct CreatePuzzleClearAgentSessionArgs { + pub input: PuzzleClearAgentSessionCreateInput, +} + +impl __sdk::InModule for CreatePuzzleClearAgentSessionArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `create_puzzle_clear_agent_session`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait create_puzzle_clear_agent_session { + fn create_puzzle_clear_agent_session(&self, input: PuzzleClearAgentSessionCreateInput) { + self.create_puzzle_clear_agent_session_then(input, |_, _| {}); + } + + fn create_puzzle_clear_agent_session_then( + &self, + input: PuzzleClearAgentSessionCreateInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl create_puzzle_clear_agent_session for super::RemoteProcedures { + fn create_puzzle_clear_agent_session_then( + &self, + input: PuzzleClearAgentSessionCreateInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearAgentSessionProcedureResult>( + "create_puzzle_clear_agent_session", + CreatePuzzleClearAgentSessionArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_square_hole_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_square_hole_agent_session_procedure.rs index cd5403a5..d58dbfa6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_square_hole_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_square_hole_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_square_hole_agent_session { input: SquareHoleAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_square_hole_agent_session for super::RemoteProcedures { input: SquareHoleAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_visual_novel_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_visual_novel_agent_session_procedure.rs index ef82cfc1..c42cc766 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_visual_novel_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_visual_novel_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_visual_novel_agent_session { input: VisualNovelAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_visual_novel_agent_session for super::RemoteProcedures { input: VisualNovelAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/create_wooden_fish_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/create_wooden_fish_agent_session_procedure.rs index aabcde5c..ec6bec0d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/create_wooden_fish_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/create_wooden_fish_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait create_wooden_fish_agent_session { input: WoodenFishAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl create_wooden_fish_agent_session for super::RemoteProcedures { input: WoodenFishAgentSessionCreateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_bark_battle_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_bark_battle_work_procedure.rs new file mode 100644 index 00000000..f164baec --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_bark_battle_work_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::bark_battle_procedure_result_type::BarkBattleProcedureResult; +use super::bark_battle_work_delete_input_type::BarkBattleWorkDeleteInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct DeleteBarkBattleWorkArgs { + pub input: BarkBattleWorkDeleteInput, +} + +impl __sdk::InModule for DeleteBarkBattleWorkArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `delete_bark_battle_work`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait delete_bark_battle_work { + fn delete_bark_battle_work(&self, input: BarkBattleWorkDeleteInput) { + self.delete_bark_battle_work_then(input, |_, _| {}); + } + + fn delete_bark_battle_work_then( + &self, + input: BarkBattleWorkDeleteInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl delete_bark_battle_work for super::RemoteProcedures { + fn delete_bark_battle_work_then( + &self, + input: BarkBattleWorkDeleteInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( + "delete_bark_battle_work", + DeleteBarkBattleWorkArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs index 51cc224f..d2be83ac 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_big_fish_work { input: BigFishWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_big_fish_work for super::RemoteProcedures { input: BigFishWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs index 27341561..39887830 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_custom_world_agent_session { input: CustomWorldAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_custom_world_agent_session for super::RemoteProcedures { input: CustomWorldAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldWorksListResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs index 246d4d00..5dc9da2f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_custom_world_profile_and_return { input: CustomWorldProfileDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_custom_world_profile_and_return for super::RemoteProcedures { input: CustomWorldProfileDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldProfileListResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_jump_hop_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_jump_hop_work_procedure.rs new file mode 100644 index 00000000..407411c0 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_jump_hop_work_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::jump_hop_work_delete_input_type::JumpHopWorkDeleteInput; +use super::jump_hop_works_procedure_result_type::JumpHopWorksProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct DeleteJumpHopWorkArgs { + pub input: JumpHopWorkDeleteInput, +} + +impl __sdk::InModule for DeleteJumpHopWorkArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `delete_jump_hop_work`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait delete_jump_hop_work { + fn delete_jump_hop_work(&self, input: JumpHopWorkDeleteInput) { + self.delete_jump_hop_work_then(input, |_, _| {}); + } + + fn delete_jump_hop_work_then( + &self, + input: JumpHopWorkDeleteInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl delete_jump_hop_work for super::RemoteProcedures { + fn delete_jump_hop_work_then( + &self, + input: JumpHopWorkDeleteInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, JumpHopWorksProcedureResult>( + "delete_jump_hop_work", + DeleteJumpHopWorkArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_match_3_d_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_match_3_d_work_procedure.rs index c26b2dc8..c87cd16a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_match_3_d_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_match_3_d_work_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_match_3_d_work { input: Match3DWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_match_3_d_work for super::RemoteProcedures { input: Match3DWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs index 5b7e5375..fc8152c5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_puzzle_work { input: PuzzleWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_puzzle_work for super::RemoteProcedures { input: PuzzleWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs index 6373a19b..9173255a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_runtime_snapshot_and_return { input: RuntimeSnapshotDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_runtime_snapshot_and_return for super::RemoteProcedures { input: RuntimeSnapshotDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeSnapshotProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_square_hole_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_square_hole_work_procedure.rs index 3469c314..3a8db794 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_square_hole_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_square_hole_work_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_square_hole_work { input: SquareHoleWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_square_hole_work for super::RemoteProcedures { input: SquareHoleWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_visual_novel_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_visual_novel_work_procedure.rs index 61ed9904..eac6ceac 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/delete_visual_novel_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_visual_novel_work_procedure.rs @@ -31,10 +31,10 @@ pub trait delete_visual_novel_work { input: VisualNovelWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl delete_visual_novel_work for super::RemoteProcedures { input: VisualNovelWorkDeleteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/delete_wooden_fish_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/delete_wooden_fish_work_procedure.rs new file mode 100644 index 00000000..7ba06da3 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/delete_wooden_fish_work_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::wooden_fish_work_delete_input_type::WoodenFishWorkDeleteInput; +use super::wooden_fish_works_procedure_result_type::WoodenFishWorksProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct DeleteWoodenFishWorkArgs { + pub input: WoodenFishWorkDeleteInput, +} + +impl __sdk::InModule for DeleteWoodenFishWorkArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `delete_wooden_fish_work`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait delete_wooden_fish_work { + fn delete_wooden_fish_work(&self, input: WoodenFishWorkDeleteInput) { + self.delete_wooden_fish_work_then(input, |_, _| {}); + } + + fn delete_wooden_fish_work_then( + &self, + input: WoodenFishWorkDeleteInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl delete_wooden_fish_work for super::RemoteProcedures { + fn delete_wooden_fish_work_then( + &self, + input: WoodenFishWorkDeleteInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, WoodenFishWorksProcedureResult>( + "delete_wooden_fish_work", + DeleteWoodenFishWorkArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs index daad89bd..1207f7b5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs @@ -31,10 +31,10 @@ pub trait drag_puzzle_piece_or_group { input: PuzzleRunDragInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl drag_puzzle_piece_or_group for super::RemoteProcedures { input: PuzzleRunDragInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/drop_square_hole_shape_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/drop_square_hole_shape_procedure.rs index 91d6bb61..4056e06a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/drop_square_hole_shape_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/drop_square_hole_shape_procedure.rs @@ -31,10 +31,10 @@ pub trait drop_square_hole_shape { input: SquareHoleRunDropInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl drop_square_hole_shape for super::RemoteProcedures { input: SquareHoleRunDropInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleDropShapeProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/ensure_analytics_date_dimension_for_date_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/ensure_analytics_date_dimension_for_date_reducer.rs index 30b9ba35..a6ea3098 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/ensure_analytics_date_dimension_for_date_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/ensure_analytics_date_dimension_for_date_reducer.rs @@ -50,9 +50,11 @@ pub trait ensure_analytics_date_dimension_for_date { &self, input: AnalyticsDateDimensionEnsureInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl ensure_analytics_date_dimension_for_date for super::RemoteReducers { &self, input: AnalyticsDateDimensionEnsureInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp.invoke_reducer_with_callback( EnsureAnalyticsDateDimensionForDateArgs { input }, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs index e778877c..c1008466 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs @@ -31,10 +31,10 @@ pub trait execute_custom_world_agent_action { input: CustomWorldAgentActionExecuteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl execute_custom_world_agent_action for super::RemoteProcedures { input: CustomWorldAgentActionExecuteInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentActionExecuteResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs index 9f8842ad..9e8059fa 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs @@ -27,10 +27,10 @@ pub trait export_auth_store_snapshot_from_tables { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -39,10 +39,10 @@ impl export_auth_store_snapshot_from_tables for super::RemoteProcedures { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AuthStoreSnapshotProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs index 3dfe18f8..d850737b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs @@ -31,10 +31,10 @@ pub trait export_database_migration_to_file { input: DatabaseMigrationExportInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl export_database_migration_to_file for super::RemoteProcedures { input: DatabaseMigrationExportInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs index 46090a01..3194799b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait fail_ai_task_and_return { input: AiTaskFailureInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl fail_ai_task_and_return for super::RemoteProcedures { input: AiTaskFailureInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AiTaskProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs index 9f5c8e7a..a2dd9fd5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs @@ -31,10 +31,10 @@ pub trait finalize_big_fish_agent_message_turn { input: BigFishMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finalize_big_fish_agent_message_turn for super::RemoteProcedures { input: BigFishMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs index fad75a7b..f670f9b6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs @@ -34,10 +34,10 @@ pub trait finalize_custom_world_agent_message_turn { input: CustomWorldAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl finalize_custom_world_agent_message_turn for super::RemoteProcedures { input: CustomWorldAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentOperationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finalize_match_3_d_agent_message_turn_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finalize_match_3_d_agent_message_turn_procedure.rs index 9d51ab95..ea0ec225 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finalize_match_3_d_agent_message_turn_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finalize_match_3_d_agent_message_turn_procedure.rs @@ -31,10 +31,10 @@ pub trait finalize_match_3_d_agent_message_turn { input: Match3DAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finalize_match_3_d_agent_message_turn for super::RemoteProcedures { input: Match3DAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs index 0014d394..7f06aafa 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs @@ -31,10 +31,10 @@ pub trait finalize_puzzle_agent_message_turn { input: PuzzleAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finalize_puzzle_agent_message_turn for super::RemoteProcedures { input: PuzzleAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finalize_square_hole_agent_message_turn_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finalize_square_hole_agent_message_turn_procedure.rs index 75808f10..350f160d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finalize_square_hole_agent_message_turn_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finalize_square_hole_agent_message_turn_procedure.rs @@ -31,10 +31,10 @@ pub trait finalize_square_hole_agent_message_turn { input: SquareHoleAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finalize_square_hole_agent_message_turn for super::RemoteProcedures { input: SquareHoleAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finalize_visual_novel_agent_message_turn_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finalize_visual_novel_agent_message_turn_procedure.rs index 5305f8e8..08b47560 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finalize_visual_novel_agent_message_turn_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finalize_visual_novel_agent_message_turn_procedure.rs @@ -34,10 +34,10 @@ pub trait finalize_visual_novel_agent_message_turn { input: VisualNovelAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl finalize_visual_novel_agent_message_turn for super::RemoteProcedures { input: VisualNovelAgentMessageFinalizeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finish_bark_battle_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finish_bark_battle_run_procedure.rs index 8eeaf0c1..28fc7ef3 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finish_bark_battle_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finish_bark_battle_run_procedure.rs @@ -31,10 +31,10 @@ pub trait finish_bark_battle_run { input: BarkBattleRunFinishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finish_bark_battle_run for super::RemoteProcedures { input: BarkBattleRunFinishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finish_match_3_d_time_up_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finish_match_3_d_time_up_procedure.rs index 0d68dbd9..bd849631 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finish_match_3_d_time_up_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finish_match_3_d_time_up_procedure.rs @@ -31,10 +31,10 @@ pub trait finish_match_3_d_time_up { input: Match3DRunTimeUpInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finish_match_3_d_time_up for super::RemoteProcedures { input: Match3DRunTimeUpInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finish_square_hole_time_up_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finish_square_hole_time_up_procedure.rs index f3b5ffc3..3ca46a0d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finish_square_hole_time_up_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finish_square_hole_time_up_procedure.rs @@ -31,10 +31,10 @@ pub trait finish_square_hole_time_up { input: SquareHoleRunTimeUpInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finish_square_hole_time_up for super::RemoteProcedures { input: SquareHoleRunTimeUpInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/finish_wooden_fish_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/finish_wooden_fish_run_procedure.rs index 240541fb..30e452e1 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/finish_wooden_fish_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/finish_wooden_fish_run_procedure.rs @@ -31,10 +31,10 @@ pub trait finish_wooden_fish_run { input: WoodenFishRunFinishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl finish_wooden_fish_run for super::RemoteProcedures { input: WoodenFishRunFinishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs index 1a87c951..144c5d40 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs @@ -31,10 +31,10 @@ pub trait generate_big_fish_asset { input: BigFishAssetGenerateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl generate_big_fish_asset for super::RemoteProcedures { input: BigFishAssetGenerateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_run_procedure.rs index 87c3f82a..bec98ca8 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_bark_battle_run { input: BarkBattleRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_bark_battle_run for super::RemoteProcedures { input: BarkBattleRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_runtime_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_runtime_config_procedure.rs index f163d3b3..6e4364f2 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_runtime_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_bark_battle_runtime_config_procedure.rs @@ -31,10 +31,10 @@ pub trait get_bark_battle_runtime_config { input: BarkBattleRuntimeConfigGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_bark_battle_runtime_config for super::RemoteProcedures { input: BarkBattleRuntimeConfigGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs index a737fbdf..0fcc276c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs @@ -31,10 +31,10 @@ pub trait get_battle_state { input: BattleStateQueryInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_battle_state for super::RemoteProcedures { input: BattleStateQueryInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BattleStateProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_run_procedure.rs index 867a6759..9a601ff2 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_big_fish_run { input: BigFishRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_big_fish_run for super::RemoteProcedures { input: BigFishRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs index 0b0d78f1..7f52f94b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_big_fish_session { input: BigFishSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_big_fish_session for super::RemoteProcedures { input: BigFishSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs index eef158dc..18f9ae27 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs @@ -31,10 +31,10 @@ pub trait get_chapter_progression { input: ChapterProgressionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_chapter_progression for super::RemoteProcedures { input: ChapterProgressionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, ChapterProgressionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_creation_entry_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_creation_entry_config_procedure.rs index eb840cdc..8a3a38dd 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_creation_entry_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_creation_entry_config_procedure.rs @@ -27,10 +27,10 @@ pub trait get_creation_entry_config { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -39,10 +39,10 @@ impl get_creation_entry_config for super::RemoteProcedures { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CreationEntryConfigProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs index 11a90329..e1034345 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_custom_world_agent_card_detail { input: CustomWorldAgentCardDetailGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_custom_world_agent_card_detail for super::RemoteProcedures { input: CustomWorldAgentCardDetailGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldDraftCardDetailResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs index 1c4ffd6a..cce1dbb5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs @@ -31,10 +31,10 @@ pub trait get_custom_world_agent_operation { input: CustomWorldAgentOperationGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_custom_world_agent_operation for super::RemoteProcedures { input: CustomWorldAgentOperationGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentOperationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs index 212987e4..f4b678e9 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_custom_world_agent_session { input: CustomWorldAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_custom_world_agent_session for super::RemoteProcedures { input: CustomWorldAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs index 24768c43..a387cbaf 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs @@ -31,10 +31,10 @@ pub trait get_custom_world_gallery_detail_by_code { input: CustomWorldGalleryDetailByCodeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_custom_world_gallery_detail_by_code for super::RemoteProcedures { input: CustomWorldGalleryDetailByCodeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs index f5127dcf..d0e029ff 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_custom_world_gallery_detail { input: CustomWorldGalleryDetailInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_custom_world_gallery_detail for super::RemoteProcedures { input: CustomWorldGalleryDetailInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs index ab99274a..82bd1c3c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_custom_world_library_detail { input: CustomWorldLibraryDetailInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_custom_world_library_detail for super::RemoteProcedures { input: CustomWorldLibraryDetailInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_agent_session_procedure.rs index fde5cf93..482aa1a5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_jump_hop_agent_session { input: JumpHopAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_jump_hop_agent_session for super::RemoteProcedures { input: JumpHopAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_run_procedure.rs index 9f641d0f..5c301da7 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_jump_hop_run { input: JumpHopRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_jump_hop_run for super::RemoteProcedures { input: JumpHopRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_work_profile_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_work_profile_procedure.rs index 62515a28..fd1fbd3e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_work_profile_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_jump_hop_work_profile_procedure.rs @@ -31,10 +31,10 @@ pub trait get_jump_hop_work_profile { input: JumpHopWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_jump_hop_work_profile for super::RemoteProcedures { input: JumpHopWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_agent_session_procedure.rs index 62d093b5..574012d9 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_match_3_d_agent_session { input: Match3DAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_match_3_d_agent_session for super::RemoteProcedures { input: Match3DAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_run_procedure.rs index 033e620c..0a472230 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_match_3_d_run { input: Match3DRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_match_3_d_run for super::RemoteProcedures { input: Match3DRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_work_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_work_detail_procedure.rs index 5a74982b..0ea9f495 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_work_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_match_3_d_work_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_match_3_d_work_detail { input: Match3DWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_match_3_d_work_detail for super::RemoteProcedures { input: Match3DWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs index 97c139fb..38a1525a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs @@ -31,10 +31,10 @@ pub trait get_player_progression_or_default { input: PlayerProgressionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_player_progression_or_default for super::RemoteProcedures { input: PlayerProgressionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PlayerProgressionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs index 38200b75..6c48fafb 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs @@ -31,10 +31,10 @@ pub trait get_profile_dashboard { input: RuntimeProfileDashboardGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_profile_dashboard for super::RemoteProcedures { input: RuntimeProfileDashboardGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileDashboardProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs index 2ad47ed2..088f4812 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs @@ -31,10 +31,10 @@ pub trait get_profile_play_stats { input: RuntimeProfilePlayStatsGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_profile_play_stats for super::RemoteProcedures { input: RuntimeProfilePlayStatsGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfilePlayStatsProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs index bf070c9c..3e42f3d5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs @@ -31,10 +31,10 @@ pub trait get_profile_recharge_center { input: RuntimeProfileRechargeCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_profile_recharge_center for super::RemoteProcedures { input: RuntimeProfileRechargeCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRechargeCenterProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_order_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_order_and_return_procedure.rs index 437f0048..f187bc6f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_order_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_order_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait get_profile_recharge_order_and_return { input: RuntimeProfileRechargeOrderGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_profile_recharge_order_and_return for super::RemoteProcedures { input: RuntimeProfileRechargeOrderGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRechargeCenterProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs index 2b3dcdad..c7221484 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs @@ -31,10 +31,10 @@ pub trait get_profile_referral_invite_center { input: RuntimeReferralInviteCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_profile_referral_invite_center for super::RemoteProcedures { input: RuntimeReferralInviteCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeReferralInviteCenterProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_task_center_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_task_center_procedure.rs index 105a4f98..0aa83260 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_profile_task_center_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_profile_task_center_procedure.rs @@ -31,10 +31,10 @@ pub trait get_profile_task_center { input: RuntimeProfileTaskCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_profile_task_center for super::RemoteProcedures { input: RuntimeProfileTaskCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileTaskCenterProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs index 97929382..8aa5a78f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_puzzle_agent_session { input: PuzzleAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_puzzle_agent_session for super::RemoteProcedures { input: PuzzleAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_agent_session_procedure.rs new file mode 100644 index 00000000..34c2f8e3 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_agent_session_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_agent_session_get_input_type::PuzzleClearAgentSessionGetInput; +use super::puzzle_clear_agent_session_procedure_result_type::PuzzleClearAgentSessionProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct GetPuzzleClearAgentSessionArgs { + pub input: PuzzleClearAgentSessionGetInput, +} + +impl __sdk::InModule for GetPuzzleClearAgentSessionArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `get_puzzle_clear_agent_session`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait get_puzzle_clear_agent_session { + fn get_puzzle_clear_agent_session(&self, input: PuzzleClearAgentSessionGetInput) { + self.get_puzzle_clear_agent_session_then(input, |_, _| {}); + } + + fn get_puzzle_clear_agent_session_then( + &self, + input: PuzzleClearAgentSessionGetInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl get_puzzle_clear_agent_session for super::RemoteProcedures { + fn get_puzzle_clear_agent_session_then( + &self, + input: PuzzleClearAgentSessionGetInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearAgentSessionProcedureResult>( + "get_puzzle_clear_agent_session", + GetPuzzleClearAgentSessionArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_runtime_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_runtime_run_procedure.rs new file mode 100644 index 00000000..7a196e1a --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_runtime_run_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_run_get_input_type::PuzzleClearRunGetInput; +use super::puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct GetPuzzleClearRuntimeRunArgs { + pub input: PuzzleClearRunGetInput, +} + +impl __sdk::InModule for GetPuzzleClearRuntimeRunArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `get_puzzle_clear_runtime_run`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait get_puzzle_clear_runtime_run { + fn get_puzzle_clear_runtime_run(&self, input: PuzzleClearRunGetInput) { + self.get_puzzle_clear_runtime_run_then(input, |_, _| {}); + } + + fn get_puzzle_clear_runtime_run_then( + &self, + input: PuzzleClearRunGetInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl get_puzzle_clear_runtime_run for super::RemoteProcedures { + fn get_puzzle_clear_runtime_run_then( + &self, + input: PuzzleClearRunGetInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearRunProcedureResult>( + "get_puzzle_clear_runtime_run", + GetPuzzleClearRuntimeRunArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_work_profile_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_work_profile_procedure.rs new file mode 100644 index 00000000..a394e9e8 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_clear_work_profile_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_work_get_input_type::PuzzleClearWorkGetInput; +use super::puzzle_clear_work_procedure_result_type::PuzzleClearWorkProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct GetPuzzleClearWorkProfileArgs { + pub input: PuzzleClearWorkGetInput, +} + +impl __sdk::InModule for GetPuzzleClearWorkProfileArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `get_puzzle_clear_work_profile`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait get_puzzle_clear_work_profile { + fn get_puzzle_clear_work_profile(&self, input: PuzzleClearWorkGetInput) { + self.get_puzzle_clear_work_profile_then(input, |_, _| {}); + } + + fn get_puzzle_clear_work_profile_then( + &self, + input: PuzzleClearWorkGetInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl get_puzzle_clear_work_profile for super::RemoteProcedures { + fn get_puzzle_clear_work_profile_then( + &self, + input: PuzzleClearWorkGetInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearWorkProcedureResult>( + "get_puzzle_clear_work_profile", + GetPuzzleClearWorkProfileArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs index a1471eb3..85b70081 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_puzzle_gallery_detail { input: PuzzleWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_puzzle_gallery_detail for super::RemoteProcedures { input: PuzzleWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs index 2db5ab66..d09fc285 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_puzzle_run { input: PuzzleRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_puzzle_run for super::RemoteProcedures { input: PuzzleRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs index d36c7417..0d6c4f70 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_puzzle_work_detail { input: PuzzleWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_puzzle_work_detail for super::RemoteProcedures { input: PuzzleWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs index abbf4f20..c8dfefac 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs @@ -31,10 +31,10 @@ pub trait get_runtime_inventory_state { input: RuntimeInventoryStateQueryInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_runtime_inventory_state for super::RemoteProcedures { input: RuntimeInventoryStateQueryInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeInventoryStateProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs index 261caed1..4ca8b03e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs @@ -31,10 +31,10 @@ pub trait get_runtime_setting_or_default { input: RuntimeSettingGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_runtime_setting_or_default for super::RemoteProcedures { input: RuntimeSettingGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeSettingProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs index 989fa40e..7f9feb4f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs @@ -31,10 +31,10 @@ pub trait get_runtime_snapshot { input: RuntimeSnapshotGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_runtime_snapshot for super::RemoteProcedures { input: RuntimeSnapshotGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeSnapshotProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_agent_session_procedure.rs index 46a17280..1db6459e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_square_hole_agent_session { input: SquareHoleAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_square_hole_agent_session for super::RemoteProcedures { input: SquareHoleAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_run_procedure.rs index 6dabdb92..5082e790 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_square_hole_run { input: SquareHoleRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_square_hole_run for super::RemoteProcedures { input: SquareHoleRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_work_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_work_detail_procedure.rs index 78812cf7..67a16a07 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_work_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_square_hole_work_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_square_hole_work_detail { input: SquareHoleWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_square_hole_work_detail for super::RemoteProcedures { input: SquareHoleWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs index 7e566dc9..44b48ada 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs @@ -31,10 +31,10 @@ pub trait get_story_session_state { input: StorySessionStateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_story_session_state for super::RemoteProcedures { input: StorySessionStateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, StorySessionStateProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_agent_session_procedure.rs index 7b50fd29..a4b4b6d1 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_visual_novel_agent_session { input: VisualNovelAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_visual_novel_agent_session for super::RemoteProcedures { input: VisualNovelAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_run_procedure.rs index 702737fd..86bdff38 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_visual_novel_run { input: VisualNovelRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_visual_novel_run for super::RemoteProcedures { input: VisualNovelRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_work_detail_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_work_detail_procedure.rs index 373121fc..64864f29 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_work_detail_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_visual_novel_work_detail_procedure.rs @@ -31,10 +31,10 @@ pub trait get_visual_novel_work_detail { input: VisualNovelWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_visual_novel_work_detail for super::RemoteProcedures { input: VisualNovelWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_agent_session_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_agent_session_procedure.rs index 8d198353..e0b80d0e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_agent_session_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_agent_session_procedure.rs @@ -31,10 +31,10 @@ pub trait get_wooden_fish_agent_session { input: WoodenFishAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_wooden_fish_agent_session for super::RemoteProcedures { input: WoodenFishAgentSessionGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_run_procedure.rs index bb381e48..8a0e145a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_run_procedure.rs @@ -31,10 +31,10 @@ pub trait get_wooden_fish_run { input: WoodenFishRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_wooden_fish_run for super::RemoteProcedures { input: WoodenFishRunGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_work_profile_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_work_profile_procedure.rs index 1621290a..44f67362 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_work_profile_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/get_wooden_fish_work_profile_procedure.rs @@ -31,10 +31,10 @@ pub trait get_wooden_fish_work_profile { input: WoodenFishWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl get_wooden_fish_work_profile for super::RemoteProcedures { input: WoodenFishWorkGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/grant_new_user_registration_wallet_reward_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/grant_new_user_registration_wallet_reward_procedure.rs index c1d7b6de..71e48151 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/grant_new_user_registration_wallet_reward_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/grant_new_user_registration_wallet_reward_procedure.rs @@ -31,10 +31,10 @@ pub trait grant_new_user_registration_wallet_reward { input: RuntimeProfileDashboardGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl grant_new_user_registration_wallet_reward for super::RemoteProcedures { input: RuntimeProfileDashboardGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileWalletAdjustmentProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs index 4c67da63..a3f2aa9e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait grant_player_progression_experience_and_return { input: PlayerProgressionGrantInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl grant_player_progression_experience_and_return for super::RemoteProcedures input: PlayerProgressionGrantInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PlayerProgressionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_reducer.rs index 83b48cf7..bd07115e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_reducer.rs @@ -50,9 +50,11 @@ pub trait grant_player_progression_experience { &self, input: PlayerProgressionGrantInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl grant_player_progression_experience for super::RemoteReducers { &self, input: PlayerProgressionGrantInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(GrantPlayerProgressionExperienceArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_json_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_json_procedure.rs index 68171d4d..3cd6d71f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_json_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_json_procedure.rs @@ -31,10 +31,10 @@ pub trait import_auth_store_snapshot_json { input: AuthStoreSnapshotUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl import_auth_store_snapshot_json for super::RemoteProcedures { input: AuthStoreSnapshotUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AuthStoreSnapshotImportProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_chunks_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_chunks_procedure.rs index 73157480..080dda54 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_chunks_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_chunks_procedure.rs @@ -31,10 +31,10 @@ pub trait import_database_migration_from_chunks { input: DatabaseMigrationImportChunksInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl import_database_migration_from_chunks for super::RemoteProcedures { input: DatabaseMigrationImportChunksInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs index 7b2322ee..2ce4ee2a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs @@ -31,10 +31,10 @@ pub trait import_database_migration_from_file { input: DatabaseMigrationImportInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl import_database_migration_from_file for super::RemoteProcedures { input: DatabaseMigrationImportInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_chunks_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_chunks_procedure.rs index 51ff565c..bbe49357 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_chunks_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_chunks_procedure.rs @@ -34,10 +34,10 @@ pub trait import_database_migration_incremental_from_chunks { input: DatabaseMigrationImportChunksInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl import_database_migration_incremental_from_chunks for super::RemoteProcedur input: DatabaseMigrationImportChunksInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs index 2fc31804..f911c87f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs @@ -31,10 +31,10 @@ pub trait import_database_migration_incremental_from_file { input: DatabaseMigrationImportInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl import_database_migration_incremental_from_file for super::RemoteProcedures input: DatabaseMigrationImportInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_jump_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_jump_procedure.rs index 19cfbec9..1535f96f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_jump_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_jump_procedure.rs @@ -31,10 +31,10 @@ pub trait jump_hop_jump { input: JumpHopRunJumpInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl jump_hop_jump for super::RemoteProcedures { input: JumpHopRunJumpInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_work_delete_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_work_delete_input_type.rs new file mode 100644 index 00000000..d5996933 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/jump_hop_work_delete_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct JumpHopWorkDeleteInput { + pub profile_id: String, + pub owner_user_id: String, +} + +impl __sdk::InModule for JumpHopWorkDeleteInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs index bcc2a742..ea689b10 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait list_asset_history_and_return { input: AssetHistoryListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_asset_history_and_return for super::RemoteProcedures { input: AssetHistoryListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AssetHistoryListResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs index 8e4c21ba..45ba04af 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_big_fish_works { input: BigFishWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_big_fish_works for super::RemoteProcedures { input: BigFishWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs index 63ee059f..01f6cb0c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs @@ -27,10 +27,10 @@ pub trait list_custom_world_gallery_entries { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -39,10 +39,10 @@ impl list_custom_world_gallery_entries for super::RemoteProcedures { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldGalleryListResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs index f8834945..c42ce2c6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs @@ -31,10 +31,10 @@ pub trait list_custom_world_profiles { input: CustomWorldProfileListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_custom_world_profiles for super::RemoteProcedures { input: CustomWorldProfileListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldProfileListResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs index d469f660..77f48ba6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_custom_world_works { input: CustomWorldWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_custom_world_works for super::RemoteProcedures { input: CustomWorldWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldWorksListResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_jump_hop_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_jump_hop_works_procedure.rs index 96d931c3..18b5cce5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_jump_hop_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_jump_hop_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_jump_hop_works { input: JumpHopWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_jump_hop_works for super::RemoteProcedures { input: JumpHopWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_match_3_d_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_match_3_d_works_procedure.rs index b1477034..c593e848 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_match_3_d_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_match_3_d_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_match_3_d_works { input: Match3DWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_match_3_d_works for super::RemoteProcedures { input: Match3DWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs index 0d368a99..00176656 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs @@ -31,10 +31,10 @@ pub trait list_platform_browse_history { input: RuntimeBrowseHistoryListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_platform_browse_history for super::RemoteProcedures { input: RuntimeBrowseHistoryListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeBrowseHistoryProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs index 31c214bb..1c7176cf 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs @@ -31,10 +31,10 @@ pub trait list_profile_save_archives { input: RuntimeProfileSaveArchiveListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_profile_save_archives for super::RemoteProcedures { input: RuntimeProfileSaveArchiveListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileSaveArchiveProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs index 23496701..d51f0df2 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs @@ -31,10 +31,10 @@ pub trait list_profile_wallet_ledger { input: RuntimeProfileWalletLedgerListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_profile_wallet_ledger for super::RemoteProcedures { input: RuntimeProfileWalletLedgerListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileWalletLedgerProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_clear_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_clear_works_procedure.rs new file mode 100644 index 00000000..d440e9b8 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_clear_works_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_works_list_input_type::PuzzleClearWorksListInput; +use super::puzzle_clear_works_procedure_result_type::PuzzleClearWorksProcedureResult; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct ListPuzzleClearWorksArgs { + pub input: PuzzleClearWorksListInput, +} + +impl __sdk::InModule for ListPuzzleClearWorksArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `list_puzzle_clear_works`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait list_puzzle_clear_works { + fn list_puzzle_clear_works(&self, input: PuzzleClearWorksListInput) { + self.list_puzzle_clear_works_then(input, |_, _| {}); + } + + fn list_puzzle_clear_works_then( + &self, + input: PuzzleClearWorksListInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl list_puzzle_clear_works for super::RemoteProcedures { + fn list_puzzle_clear_works_then( + &self, + input: PuzzleClearWorksListInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearWorksProcedureResult>( + "list_puzzle_clear_works", + ListPuzzleClearWorksArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs index 553b8e08..e62fd064 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs @@ -27,10 +27,10 @@ pub trait list_puzzle_gallery { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -39,10 +39,10 @@ impl list_puzzle_gallery for super::RemoteProcedures { &self, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs index 844d16df..1da004e9 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_puzzle_works { input: PuzzleWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_puzzle_works for super::RemoteProcedures { input: PuzzleWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_square_hole_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_square_hole_works_procedure.rs index 0052c502..1c706a8c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_square_hole_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_square_hole_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_square_hole_works { input: SquareHoleWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_square_hole_works for super::RemoteProcedures { input: SquareHoleWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_runtime_history_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_runtime_history_procedure.rs index 680a8455..fdc06a22 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_runtime_history_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_runtime_history_procedure.rs @@ -31,10 +31,10 @@ pub trait list_visual_novel_runtime_history { input: VisualNovelRuntimeHistoryListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_visual_novel_runtime_history for super::RemoteProcedures { input: VisualNovelRuntimeHistoryListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelHistoryProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_works_procedure.rs index be9b9a29..1920ee79 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_visual_novel_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_visual_novel_works { input: VisualNovelWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_visual_novel_works for super::RemoteProcedures { input: VisualNovelWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/list_wooden_fish_works_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/list_wooden_fish_works_procedure.rs index 87695a8e..d449ff80 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/list_wooden_fish_works_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/list_wooden_fish_works_procedure.rs @@ -31,10 +31,10 @@ pub trait list_wooden_fish_works { input: WoodenFishWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl list_wooden_fish_works for super::RemoteProcedures { input: WoodenFishWorksListInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/mark_profile_recharge_order_paid_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/mark_profile_recharge_order_paid_and_return_procedure.rs index 09ba81c7..f412f184 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/mark_profile_recharge_order_paid_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/mark_profile_recharge_order_paid_and_return_procedure.rs @@ -34,10 +34,10 @@ pub trait mark_profile_recharge_order_paid_and_return { input: RuntimeProfileRechargeOrderPaidInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl mark_profile_recharge_order_paid_and_return for super::RemoteProcedures { input: RuntimeProfileRechargeOrderPaidInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRechargeCenterProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_clear_level_time_up_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_clear_level_time_up_procedure.rs new file mode 100644 index 00000000..99280a6b --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_clear_level_time_up_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; +use super::puzzle_clear_run_time_up_input_type::PuzzleClearRunTimeUpInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct MarkPuzzleClearLevelTimeUpArgs { + pub input: PuzzleClearRunTimeUpInput, +} + +impl __sdk::InModule for MarkPuzzleClearLevelTimeUpArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `mark_puzzle_clear_level_time_up`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait mark_puzzle_clear_level_time_up { + fn mark_puzzle_clear_level_time_up(&self, input: PuzzleClearRunTimeUpInput) { + self.mark_puzzle_clear_level_time_up_then(input, |_, _| {}); + } + + fn mark_puzzle_clear_level_time_up_then( + &self, + input: PuzzleClearRunTimeUpInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl mark_puzzle_clear_level_time_up for super::RemoteProcedures { + fn mark_puzzle_clear_level_time_up_then( + &self, + input: PuzzleClearRunTimeUpInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearRunProcedureResult>( + "mark_puzzle_clear_level_time_up", + MarkPuzzleClearLevelTimeUpArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_draft_generation_failed_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_draft_generation_failed_procedure.rs index 954b7ddc..ae073d5c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_draft_generation_failed_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/mark_puzzle_draft_generation_failed_procedure.rs @@ -31,10 +31,10 @@ pub trait mark_puzzle_draft_generation_failed { input: PuzzleDraftCompileFailureInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl mark_puzzle_draft_generation_failed for super::RemoteProcedures { input: PuzzleDraftCompileFailureInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/public_work_play_daily_stat_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/public_work_play_daily_stat_table.rs index 8c95aaa8..d0c84769 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/public_work_play_daily_stat_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/public_work_play_daily_stat_table.rs @@ -153,7 +153,7 @@ pub trait public_work_play_daily_statQueryTableAccess { #[allow(non_snake_case)] /// Get a query builder for the table `PublicWorkPlayDailyStat`. fn public_work_play_daily_stat(&self) - -> __sdk::__query_builder::Table; + -> __sdk::__query_builder::Table; } impl public_work_play_daily_statQueryTableAccess for __sdk::QueryTableAccessor { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_bark_battle_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_bark_battle_work_procedure.rs index 9ea9f0db..87884f54 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_bark_battle_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_bark_battle_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_bark_battle_work { input: BarkBattleWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_bark_battle_work for super::RemoteProcedures { input: BarkBattleWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs index e8007288..d4507ad8 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_big_fish_game { input: BigFishPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_big_fish_game for super::RemoteProcedures { input: BigFishPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs index e5434aca..d5741922 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_custom_world_profile_and_return { input: CustomWorldProfilePublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_custom_world_profile_and_return for super::RemoteProcedures { input: CustomWorldProfilePublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_reducer.rs index 84e6b339..2e2f71f6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_reducer.rs @@ -50,9 +50,11 @@ pub trait publish_custom_world_profile { &self, input: CustomWorldProfilePublishInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl publish_custom_world_profile for super::RemoteReducers { &self, input: CustomWorldProfilePublishInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(PublishCustomWorldProfileArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs index 1eb935a2..42c76aad 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_custom_world_world { input: CustomWorldPublishWorldInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_custom_world_world for super::RemoteProcedures { input: CustomWorldPublishWorldInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldPublishWorldResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_jump_hop_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_jump_hop_work_procedure.rs index a87bb278..926aed9b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_jump_hop_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_jump_hop_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_jump_hop_work { input: JumpHopWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_jump_hop_work for super::RemoteProcedures { input: JumpHopWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_match_3_d_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_match_3_d_work_procedure.rs index 65bd160e..db0c7efe 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_match_3_d_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_match_3_d_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_match_3_d_work { input: Match3DWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_match_3_d_work for super::RemoteProcedures { input: Match3DWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_clear_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_clear_work_procedure.rs new file mode 100644 index 00000000..55b2ca6d --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_clear_work_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_work_procedure_result_type::PuzzleClearWorkProcedureResult; +use super::puzzle_clear_work_publish_input_type::PuzzleClearWorkPublishInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct PublishPuzzleClearWorkArgs { + pub input: PuzzleClearWorkPublishInput, +} + +impl __sdk::InModule for PublishPuzzleClearWorkArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `publish_puzzle_clear_work`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait publish_puzzle_clear_work { + fn publish_puzzle_clear_work(&self, input: PuzzleClearWorkPublishInput) { + self.publish_puzzle_clear_work_then(input, |_, _| {}); + } + + fn publish_puzzle_clear_work_then( + &self, + input: PuzzleClearWorkPublishInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl publish_puzzle_clear_work for super::RemoteProcedures { + fn publish_puzzle_clear_work_then( + &self, + input: PuzzleClearWorkPublishInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearWorkProcedureResult>( + "publish_puzzle_clear_work", + PublishPuzzleClearWorkArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs index 288b44a5..932b66d6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_puzzle_work { input: PuzzlePublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_puzzle_work for super::RemoteProcedures { input: PuzzlePublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_square_hole_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_square_hole_work_procedure.rs index f0c2be48..ad4e944e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_square_hole_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_square_hole_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_square_hole_work { input: SquareHoleWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_square_hole_work for super::RemoteProcedures { input: SquareHoleWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_visual_novel_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_visual_novel_work_procedure.rs index 7ddf731b..91c7eabd 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_visual_novel_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_visual_novel_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_visual_novel_work { input: VisualNovelWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_visual_novel_work for super::RemoteProcedures { input: VisualNovelWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/publish_wooden_fish_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/publish_wooden_fish_work_procedure.rs index 5dd6555c..f370ace6 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/publish_wooden_fish_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/publish_wooden_fish_work_procedure.rs @@ -31,10 +31,10 @@ pub trait publish_wooden_fish_work { input: WoodenFishWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl publish_wooden_fish_work for super::RemoteProcedures { input: WoodenFishWorkPublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/put_database_migration_import_chunk_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/put_database_migration_import_chunk_procedure.rs index 597b2511..f3776bfd 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/put_database_migration_import_chunk_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/put_database_migration_import_chunk_procedure.rs @@ -31,10 +31,10 @@ pub trait put_database_migration_import_chunk { input: DatabaseMigrationImportChunkInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl put_database_migration_import_chunk for super::RemoteProcedures { input: DatabaseMigrationImportChunkInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_create_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_create_input_type.rs new file mode 100644 index 00000000..9403c138 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_create_input_type.rs @@ -0,0 +1,23 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearAgentSessionCreateInput { + pub session_id: String, + pub owner_user_id: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: Option, + pub board_background_prompt: String, + pub created_at_micros: i64, +} + +impl __sdk::InModule for PuzzleClearAgentSessionCreateInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_get_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_get_input_type.rs new file mode 100644 index 00000000..3b88ac38 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_get_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearAgentSessionGetInput { + pub session_id: String, + pub owner_user_id: String, +} + +impl __sdk::InModule for PuzzleClearAgentSessionGetInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_procedure_result_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_procedure_result_type.rs new file mode 100644 index 00000000..ac1a377f --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_procedure_result_type.rs @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_agent_session_snapshot_type::PuzzleClearAgentSessionSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearAgentSessionProcedureResult { + pub ok: bool, + pub session: Option, + pub error_message: Option, +} + +impl __sdk::InModule for PuzzleClearAgentSessionProcedureResult { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_row_type.rs new file mode 100644 index 00000000..697bb922 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_row_type.rs @@ -0,0 +1,72 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearAgentSessionRow { + pub session_id: String, + pub owner_user_id: String, + pub status: String, + pub draft_json: String, + pub published_profile_id: String, + pub created_at: __sdk::Timestamp, + pub updated_at: __sdk::Timestamp, +} + +impl __sdk::InModule for PuzzleClearAgentSessionRow { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `PuzzleClearAgentSessionRow`. +/// +/// Provides typed access to columns for query building. +pub struct PuzzleClearAgentSessionRowCols { + pub session_id: __sdk::__query_builder::Col, + pub owner_user_id: __sdk::__query_builder::Col, + pub status: __sdk::__query_builder::Col, + pub draft_json: __sdk::__query_builder::Col, + pub published_profile_id: __sdk::__query_builder::Col, + pub created_at: __sdk::__query_builder::Col, + pub updated_at: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for PuzzleClearAgentSessionRow { + type Cols = PuzzleClearAgentSessionRowCols; + fn cols(table_name: &'static str) -> Self::Cols { + PuzzleClearAgentSessionRowCols { + session_id: __sdk::__query_builder::Col::new(table_name, "session_id"), + owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"), + status: __sdk::__query_builder::Col::new(table_name, "status"), + draft_json: __sdk::__query_builder::Col::new(table_name, "draft_json"), + published_profile_id: __sdk::__query_builder::Col::new( + table_name, + "published_profile_id", + ), + created_at: __sdk::__query_builder::Col::new(table_name, "created_at"), + updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"), + } + } +} + +/// Indexed column accessor struct for the table `PuzzleClearAgentSessionRow`. +/// +/// Provides typed access to indexed columns for query building. +pub struct PuzzleClearAgentSessionRowIxCols { + pub owner_user_id: __sdk::__query_builder::IxCol, + pub session_id: __sdk::__query_builder::IxCol, +} + +impl __sdk::__query_builder::HasIxCols for PuzzleClearAgentSessionRow { + type IxCols = PuzzleClearAgentSessionRowIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + PuzzleClearAgentSessionRowIxCols { + owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"), + session_id: __sdk::__query_builder::IxCol::new(table_name, "session_id"), + } + } +} + +impl __sdk::__query_builder::CanBeLookupTable for PuzzleClearAgentSessionRow {} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_snapshot_type.rs new file mode 100644 index 00000000..4c31e374 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_snapshot_type.rs @@ -0,0 +1,23 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_draft_snapshot_type::PuzzleClearDraftSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearAgentSessionSnapshot { + pub session_id: String, + pub owner_user_id: String, + pub status: String, + pub draft: Option, + pub published_profile_id: Option, + pub created_at_micros: i64, + pub updated_at_micros: i64, +} + +impl __sdk::InModule for PuzzleClearAgentSessionSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_table.rs new file mode 100644 index 00000000..3b2ad913 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_agent_session_table.rs @@ -0,0 +1,166 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::puzzle_clear_agent_session_row_type::PuzzleClearAgentSessionRow; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `puzzle_clear_agent_session`. +/// +/// Obtain a handle from the [`PuzzleClearAgentSessionTableAccess::puzzle_clear_agent_session`] method on [`super::RemoteTables`], +/// like `ctx.db.puzzle_clear_agent_session()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_agent_session().on_insert(...)`. +pub struct PuzzleClearAgentSessionTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `puzzle_clear_agent_session`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PuzzleClearAgentSessionTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PuzzleClearAgentSessionTableHandle`], which mediates access to the table `puzzle_clear_agent_session`. + fn puzzle_clear_agent_session(&self) -> PuzzleClearAgentSessionTableHandle<'_>; +} + +impl PuzzleClearAgentSessionTableAccess for super::RemoteTables { + fn puzzle_clear_agent_session(&self) -> PuzzleClearAgentSessionTableHandle<'_> { + PuzzleClearAgentSessionTableHandle { + imp: self + .imp + .get_table::("puzzle_clear_agent_session"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PuzzleClearAgentSessionInsertCallbackId(__sdk::CallbackId); +pub struct PuzzleClearAgentSessionDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PuzzleClearAgentSessionTableHandle<'ctx> { + type Row = PuzzleClearAgentSessionRow; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PuzzleClearAgentSessionInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearAgentSessionInsertCallbackId { + PuzzleClearAgentSessionInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PuzzleClearAgentSessionInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PuzzleClearAgentSessionDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearAgentSessionDeleteCallbackId { + PuzzleClearAgentSessionDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PuzzleClearAgentSessionDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct PuzzleClearAgentSessionUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for PuzzleClearAgentSessionTableHandle<'ctx> { + type UpdateCallbackId = PuzzleClearAgentSessionUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> PuzzleClearAgentSessionUpdateCallbackId { + PuzzleClearAgentSessionUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: PuzzleClearAgentSessionUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `session_id` unique index on the table `puzzle_clear_agent_session`, +/// which allows point queries on the field of the same name +/// via the [`PuzzleClearAgentSessionSessionIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_agent_session().session_id().find(...)`. +pub struct PuzzleClearAgentSessionSessionIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> PuzzleClearAgentSessionTableHandle<'ctx> { + /// Get a handle on the `session_id` unique index on the table `puzzle_clear_agent_session`. + pub fn session_id(&self) -> PuzzleClearAgentSessionSessionIdUnique<'ctx> { + PuzzleClearAgentSessionSessionIdUnique { + imp: self.imp.get_unique_constraint::("session_id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> PuzzleClearAgentSessionSessionIdUnique<'ctx> { + /// Find the subscribed row whose `session_id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &String) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = + client_cache.get_or_make_table::("puzzle_clear_agent_session"); + _table.add_unique_constraint::("session_id", |row| &row.session_id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `PuzzleClearAgentSessionRow`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait puzzle_clear_agent_sessionQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `PuzzleClearAgentSessionRow`. + fn puzzle_clear_agent_session( + &self, + ) -> __sdk::__query_builder::Table; +} + +impl puzzle_clear_agent_sessionQueryTableAccess for __sdk::QueryTableAccessor { + fn puzzle_clear_agent_session( + &self, + ) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("puzzle_clear_agent_session") + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_board_cell_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_board_cell_snapshot_type.rs new file mode 100644 index 00000000..6fe05368 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_board_cell_snapshot_type.rs @@ -0,0 +1,20 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearBoardCellSnapshot { + pub row: u32, + pub col: u32, + pub card: Option, + pub locked_group_id: Option, +} + +impl __sdk::InModule for PuzzleClearBoardCellSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_board_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_board_snapshot_type.rs new file mode 100644 index 00000000..fd1626e9 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_board_snapshot_type.rs @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_board_cell_snapshot_type::PuzzleClearBoardCellSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearBoardSnapshot { + pub rows: u32, + pub cols: u32, + pub cells: Vec, +} + +impl __sdk::InModule for PuzzleClearBoardSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_card_asset_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_card_asset_snapshot_type.rs new file mode 100644 index 00000000..8b40818e --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_card_asset_snapshot_type.rs @@ -0,0 +1,24 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearCardAssetSnapshot { + pub card_id: String, + pub group_id: String, + pub shape: String, + pub orientation: String, + pub part_x: u32, + pub part_y: u32, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub source_atlas_cell: String, +} + +impl __sdk::InModule for PuzzleClearCardAssetSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_draft_compile_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_draft_compile_input_type.rs new file mode 100644 index 00000000..c04959eb --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_draft_compile_input_type.rs @@ -0,0 +1,29 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearDraftCompileInput { + pub session_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: Option, + pub board_background_prompt: String, + pub atlas_asset_json: Option, + pub pattern_groups_json: Option, + pub card_assets_json: Option, + pub generation_status: Option, + pub compiled_at_micros: i64, +} + +impl __sdk::InModule for PuzzleClearDraftCompileInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_draft_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_draft_snapshot_type.rs new file mode 100644 index 00000000..75055777 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_draft_snapshot_type.rs @@ -0,0 +1,32 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; +use super::puzzle_clear_image_asset_snapshot_type::PuzzleClearImageAssetSnapshot; +use super::puzzle_clear_pattern_group_snapshot_type::PuzzleClearPatternGroupSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearDraftSnapshot { + pub template_id: String, + pub template_name: String, + pub profile_id: Option, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + pub board_background_prompt: String, + pub card_back_image_src: Option, + pub atlas_asset: Option, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub generation_status: String, +} + +impl __sdk::InModule for PuzzleClearDraftSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_event_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_event_row_type.rs new file mode 100644 index 00000000..00fcb66f --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_event_row_type.rs @@ -0,0 +1,71 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearEventRow { + pub event_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub run_id: String, + pub event_type: String, + pub result: String, + pub occurred_at: __sdk::Timestamp, +} + +impl __sdk::InModule for PuzzleClearEventRow { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `PuzzleClearEventRow`. +/// +/// Provides typed access to columns for query building. +pub struct PuzzleClearEventRowCols { + pub event_id: __sdk::__query_builder::Col, + pub owner_user_id: __sdk::__query_builder::Col, + pub profile_id: __sdk::__query_builder::Col, + pub run_id: __sdk::__query_builder::Col, + pub event_type: __sdk::__query_builder::Col, + pub result: __sdk::__query_builder::Col, + pub occurred_at: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for PuzzleClearEventRow { + type Cols = PuzzleClearEventRowCols; + fn cols(table_name: &'static str) -> Self::Cols { + PuzzleClearEventRowCols { + event_id: __sdk::__query_builder::Col::new(table_name, "event_id"), + owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"), + profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"), + run_id: __sdk::__query_builder::Col::new(table_name, "run_id"), + event_type: __sdk::__query_builder::Col::new(table_name, "event_type"), + result: __sdk::__query_builder::Col::new(table_name, "result"), + occurred_at: __sdk::__query_builder::Col::new(table_name, "occurred_at"), + } + } +} + +/// Indexed column accessor struct for the table `PuzzleClearEventRow`. +/// +/// Provides typed access to indexed columns for query building. +pub struct PuzzleClearEventRowIxCols { + pub event_id: __sdk::__query_builder::IxCol, + pub profile_id: __sdk::__query_builder::IxCol, + pub run_id: __sdk::__query_builder::IxCol, +} + +impl __sdk::__query_builder::HasIxCols for PuzzleClearEventRow { + type IxCols = PuzzleClearEventRowIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + PuzzleClearEventRowIxCols { + event_id: __sdk::__query_builder::IxCol::new(table_name, "event_id"), + profile_id: __sdk::__query_builder::IxCol::new(table_name, "profile_id"), + run_id: __sdk::__query_builder::IxCol::new(table_name, "run_id"), + } + } +} + +impl __sdk::__query_builder::CanBeLookupTable for PuzzleClearEventRow {} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_event_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_event_table.rs new file mode 100644 index 00000000..c1966f23 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_event_table.rs @@ -0,0 +1,161 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::puzzle_clear_event_row_type::PuzzleClearEventRow; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `puzzle_clear_event`. +/// +/// Obtain a handle from the [`PuzzleClearEventTableAccess::puzzle_clear_event`] method on [`super::RemoteTables`], +/// like `ctx.db.puzzle_clear_event()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_event().on_insert(...)`. +pub struct PuzzleClearEventTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `puzzle_clear_event`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PuzzleClearEventTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PuzzleClearEventTableHandle`], which mediates access to the table `puzzle_clear_event`. + fn puzzle_clear_event(&self) -> PuzzleClearEventTableHandle<'_>; +} + +impl PuzzleClearEventTableAccess for super::RemoteTables { + fn puzzle_clear_event(&self) -> PuzzleClearEventTableHandle<'_> { + PuzzleClearEventTableHandle { + imp: self + .imp + .get_table::("puzzle_clear_event"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PuzzleClearEventInsertCallbackId(__sdk::CallbackId); +pub struct PuzzleClearEventDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PuzzleClearEventTableHandle<'ctx> { + type Row = PuzzleClearEventRow; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PuzzleClearEventInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearEventInsertCallbackId { + PuzzleClearEventInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PuzzleClearEventInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PuzzleClearEventDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearEventDeleteCallbackId { + PuzzleClearEventDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PuzzleClearEventDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct PuzzleClearEventUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for PuzzleClearEventTableHandle<'ctx> { + type UpdateCallbackId = PuzzleClearEventUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> PuzzleClearEventUpdateCallbackId { + PuzzleClearEventUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: PuzzleClearEventUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `event_id` unique index on the table `puzzle_clear_event`, +/// which allows point queries on the field of the same name +/// via the [`PuzzleClearEventEventIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_event().event_id().find(...)`. +pub struct PuzzleClearEventEventIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> PuzzleClearEventTableHandle<'ctx> { + /// Get a handle on the `event_id` unique index on the table `puzzle_clear_event`. + pub fn event_id(&self) -> PuzzleClearEventEventIdUnique<'ctx> { + PuzzleClearEventEventIdUnique { + imp: self.imp.get_unique_constraint::("event_id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> PuzzleClearEventEventIdUnique<'ctx> { + /// Find the subscribed row whose `event_id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &String) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("puzzle_clear_event"); + _table.add_unique_constraint::("event_id", |row| &row.event_id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `PuzzleClearEventRow`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait puzzle_clear_eventQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `PuzzleClearEventRow`. + fn puzzle_clear_event(&self) -> __sdk::__query_builder::Table; +} + +impl puzzle_clear_eventQueryTableAccess for __sdk::QueryTableAccessor { + fn puzzle_clear_event(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("puzzle_clear_event") + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_card_view_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_card_view_row_type.rs new file mode 100644 index 00000000..575c7f92 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_card_view_row_type.rs @@ -0,0 +1,77 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearGalleryCardViewRow { + pub public_work_code: String, + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub cover_image_src: Option, + pub publication_status: String, + pub play_count: u32, + pub updated_at_micros: i64, + pub published_at_micros: Option, + pub generation_status: String, +} + +impl __sdk::InModule for PuzzleClearGalleryCardViewRow { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `PuzzleClearGalleryCardViewRow`. +/// +/// Provides typed access to columns for query building. +pub struct PuzzleClearGalleryCardViewRowCols { + pub public_work_code: __sdk::__query_builder::Col, + pub work_id: __sdk::__query_builder::Col, + pub profile_id: __sdk::__query_builder::Col, + pub owner_user_id: __sdk::__query_builder::Col, + pub author_display_name: __sdk::__query_builder::Col, + pub work_title: __sdk::__query_builder::Col, + pub work_description: __sdk::__query_builder::Col, + pub theme_prompt: __sdk::__query_builder::Col, + pub cover_image_src: __sdk::__query_builder::Col>, + pub publication_status: __sdk::__query_builder::Col, + pub play_count: __sdk::__query_builder::Col, + pub updated_at_micros: __sdk::__query_builder::Col, + pub published_at_micros: + __sdk::__query_builder::Col>, + pub generation_status: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for PuzzleClearGalleryCardViewRow { + type Cols = PuzzleClearGalleryCardViewRowCols; + fn cols(table_name: &'static str) -> Self::Cols { + PuzzleClearGalleryCardViewRowCols { + public_work_code: __sdk::__query_builder::Col::new(table_name, "public_work_code"), + work_id: __sdk::__query_builder::Col::new(table_name, "work_id"), + profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"), + owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"), + author_display_name: __sdk::__query_builder::Col::new( + table_name, + "author_display_name", + ), + work_title: __sdk::__query_builder::Col::new(table_name, "work_title"), + work_description: __sdk::__query_builder::Col::new(table_name, "work_description"), + theme_prompt: __sdk::__query_builder::Col::new(table_name, "theme_prompt"), + cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"), + publication_status: __sdk::__query_builder::Col::new(table_name, "publication_status"), + play_count: __sdk::__query_builder::Col::new(table_name, "play_count"), + updated_at_micros: __sdk::__query_builder::Col::new(table_name, "updated_at_micros"), + published_at_micros: __sdk::__query_builder::Col::new( + table_name, + "published_at_micros", + ), + generation_status: __sdk::__query_builder::Col::new(table_name, "generation_status"), + } + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_card_view_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_card_view_table.rs new file mode 100644 index 00000000..1c78182b --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_card_view_table.rs @@ -0,0 +1,121 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::puzzle_clear_gallery_card_view_row_type::PuzzleClearGalleryCardViewRow; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `puzzle_clear_gallery_card_view`. +/// +/// Obtain a handle from the [`PuzzleClearGalleryCardViewTableAccess::puzzle_clear_gallery_card_view`] method on [`super::RemoteTables`], +/// like `ctx.db.puzzle_clear_gallery_card_view()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_gallery_card_view().on_insert(...)`. +pub struct PuzzleClearGalleryCardViewTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `puzzle_clear_gallery_card_view`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PuzzleClearGalleryCardViewTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PuzzleClearGalleryCardViewTableHandle`], which mediates access to the table `puzzle_clear_gallery_card_view`. + fn puzzle_clear_gallery_card_view(&self) -> PuzzleClearGalleryCardViewTableHandle<'_>; +} + +impl PuzzleClearGalleryCardViewTableAccess for super::RemoteTables { + fn puzzle_clear_gallery_card_view(&self) -> PuzzleClearGalleryCardViewTableHandle<'_> { + PuzzleClearGalleryCardViewTableHandle { + imp: self + .imp + .get_table::("puzzle_clear_gallery_card_view"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PuzzleClearGalleryCardViewInsertCallbackId(__sdk::CallbackId); +pub struct PuzzleClearGalleryCardViewDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PuzzleClearGalleryCardViewTableHandle<'ctx> { + type Row = PuzzleClearGalleryCardViewRow; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PuzzleClearGalleryCardViewInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearGalleryCardViewInsertCallbackId { + PuzzleClearGalleryCardViewInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PuzzleClearGalleryCardViewInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PuzzleClearGalleryCardViewDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearGalleryCardViewDeleteCallbackId { + PuzzleClearGalleryCardViewDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PuzzleClearGalleryCardViewDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache + .get_or_make_table::("puzzle_clear_gallery_card_view"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse( + "TableUpdate", + "TableUpdate", + ) + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `PuzzleClearGalleryCardViewRow`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait puzzle_clear_gallery_card_viewQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `PuzzleClearGalleryCardViewRow`. + fn puzzle_clear_gallery_card_view( + &self, + ) -> __sdk::__query_builder::Table; +} + +impl puzzle_clear_gallery_card_viewQueryTableAccess for __sdk::QueryTableAccessor { + fn puzzle_clear_gallery_card_view( + &self, + ) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("puzzle_clear_gallery_card_view") + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_view_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_view_row_type.rs new file mode 100644 index 00000000..884558a7 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_view_row_type.rs @@ -0,0 +1,124 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; +use super::puzzle_clear_image_asset_snapshot_type::PuzzleClearImageAssetSnapshot; +use super::puzzle_clear_pattern_group_snapshot_type::PuzzleClearPatternGroupSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearGalleryViewRow { + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub source_session_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + pub board_background_prompt: String, + pub card_back_image_src: Option, + pub atlas_asset: PuzzleClearImageAssetSnapshot, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub cover_image_src: Option, + pub publication_status: String, + pub publish_ready: bool, + pub play_count: u32, + pub generation_status: String, + pub updated_at_micros: i64, + pub published_at_micros: Option, +} + +impl __sdk::InModule for PuzzleClearGalleryViewRow { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `PuzzleClearGalleryViewRow`. +/// +/// Provides typed access to columns for query building. +pub struct PuzzleClearGalleryViewRowCols { + pub work_id: __sdk::__query_builder::Col, + pub profile_id: __sdk::__query_builder::Col, + pub owner_user_id: __sdk::__query_builder::Col, + pub source_session_id: __sdk::__query_builder::Col, + pub author_display_name: __sdk::__query_builder::Col, + pub work_title: __sdk::__query_builder::Col, + pub work_description: __sdk::__query_builder::Col, + pub theme_prompt: __sdk::__query_builder::Col, + pub generate_board_background: __sdk::__query_builder::Col, + pub board_background_asset: __sdk::__query_builder::Col< + PuzzleClearGalleryViewRow, + Option, + >, + pub board_background_prompt: __sdk::__query_builder::Col, + pub card_back_image_src: __sdk::__query_builder::Col>, + pub atlas_asset: + __sdk::__query_builder::Col, + pub pattern_groups: __sdk::__query_builder::Col< + PuzzleClearGalleryViewRow, + Vec, + >, + pub card_assets: + __sdk::__query_builder::Col>, + pub cover_image_src: __sdk::__query_builder::Col>, + pub publication_status: __sdk::__query_builder::Col, + pub publish_ready: __sdk::__query_builder::Col, + pub play_count: __sdk::__query_builder::Col, + pub generation_status: __sdk::__query_builder::Col, + pub updated_at_micros: __sdk::__query_builder::Col, + pub published_at_micros: __sdk::__query_builder::Col>, +} + +impl __sdk::__query_builder::HasCols for PuzzleClearGalleryViewRow { + type Cols = PuzzleClearGalleryViewRowCols; + fn cols(table_name: &'static str) -> Self::Cols { + PuzzleClearGalleryViewRowCols { + work_id: __sdk::__query_builder::Col::new(table_name, "work_id"), + profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"), + owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"), + source_session_id: __sdk::__query_builder::Col::new(table_name, "source_session_id"), + author_display_name: __sdk::__query_builder::Col::new( + table_name, + "author_display_name", + ), + work_title: __sdk::__query_builder::Col::new(table_name, "work_title"), + work_description: __sdk::__query_builder::Col::new(table_name, "work_description"), + theme_prompt: __sdk::__query_builder::Col::new(table_name, "theme_prompt"), + generate_board_background: __sdk::__query_builder::Col::new( + table_name, + "generate_board_background", + ), + board_background_asset: __sdk::__query_builder::Col::new( + table_name, + "board_background_asset", + ), + board_background_prompt: __sdk::__query_builder::Col::new( + table_name, + "board_background_prompt", + ), + card_back_image_src: __sdk::__query_builder::Col::new( + table_name, + "card_back_image_src", + ), + atlas_asset: __sdk::__query_builder::Col::new(table_name, "atlas_asset"), + pattern_groups: __sdk::__query_builder::Col::new(table_name, "pattern_groups"), + card_assets: __sdk::__query_builder::Col::new(table_name, "card_assets"), + cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"), + publication_status: __sdk::__query_builder::Col::new(table_name, "publication_status"), + publish_ready: __sdk::__query_builder::Col::new(table_name, "publish_ready"), + play_count: __sdk::__query_builder::Col::new(table_name, "play_count"), + generation_status: __sdk::__query_builder::Col::new(table_name, "generation_status"), + updated_at_micros: __sdk::__query_builder::Col::new(table_name, "updated_at_micros"), + published_at_micros: __sdk::__query_builder::Col::new( + table_name, + "published_at_micros", + ), + } + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_view_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_view_table.rs new file mode 100644 index 00000000..f41b3c6b --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_gallery_view_table.rs @@ -0,0 +1,120 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; +use super::puzzle_clear_gallery_view_row_type::PuzzleClearGalleryViewRow; +use super::puzzle_clear_image_asset_snapshot_type::PuzzleClearImageAssetSnapshot; +use super::puzzle_clear_pattern_group_snapshot_type::PuzzleClearPatternGroupSnapshot; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `puzzle_clear_gallery_view`. +/// +/// Obtain a handle from the [`PuzzleClearGalleryViewTableAccess::puzzle_clear_gallery_view`] method on [`super::RemoteTables`], +/// like `ctx.db.puzzle_clear_gallery_view()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_gallery_view().on_insert(...)`. +pub struct PuzzleClearGalleryViewTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `puzzle_clear_gallery_view`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PuzzleClearGalleryViewTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PuzzleClearGalleryViewTableHandle`], which mediates access to the table `puzzle_clear_gallery_view`. + fn puzzle_clear_gallery_view(&self) -> PuzzleClearGalleryViewTableHandle<'_>; +} + +impl PuzzleClearGalleryViewTableAccess for super::RemoteTables { + fn puzzle_clear_gallery_view(&self) -> PuzzleClearGalleryViewTableHandle<'_> { + PuzzleClearGalleryViewTableHandle { + imp: self + .imp + .get_table::("puzzle_clear_gallery_view"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PuzzleClearGalleryViewInsertCallbackId(__sdk::CallbackId); +pub struct PuzzleClearGalleryViewDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PuzzleClearGalleryViewTableHandle<'ctx> { + type Row = PuzzleClearGalleryViewRow; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PuzzleClearGalleryViewInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearGalleryViewInsertCallbackId { + PuzzleClearGalleryViewInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PuzzleClearGalleryViewInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PuzzleClearGalleryViewDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearGalleryViewDeleteCallbackId { + PuzzleClearGalleryViewDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PuzzleClearGalleryViewDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = + client_cache.get_or_make_table::("puzzle_clear_gallery_view"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `PuzzleClearGalleryViewRow`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait puzzle_clear_gallery_viewQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `PuzzleClearGalleryViewRow`. + fn puzzle_clear_gallery_view(&self) + -> __sdk::__query_builder::Table; +} + +impl puzzle_clear_gallery_viewQueryTableAccess for __sdk::QueryTableAccessor { + fn puzzle_clear_gallery_view( + &self, + ) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("puzzle_clear_gallery_view") + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_image_asset_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_image_asset_snapshot_type.rs new file mode 100644 index 00000000..b9cf55a5 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_image_asset_snapshot_type.rs @@ -0,0 +1,22 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearImageAssetSnapshot { + pub asset_id: String, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub generation_provider: String, + pub prompt: String, + pub width: u32, + pub height: u32, +} + +impl __sdk::InModule for PuzzleClearImageAssetSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_pattern_group_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_pattern_group_snapshot_type.rs new file mode 100644 index 00000000..dfebc41a --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_pattern_group_snapshot_type.rs @@ -0,0 +1,22 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearPatternGroupSnapshot { + pub group_id: String, + pub shape: String, + pub width: u32, + pub height: u32, + pub atlas_x: u32, + pub atlas_y: u32, + pub atlas_width: u32, + pub atlas_height: u32, +} + +impl __sdk::InModule for PuzzleClearPatternGroupSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_get_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_get_input_type.rs new file mode 100644 index 00000000..3a45f25b --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_get_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunGetInput { + pub run_id: String, + pub owner_user_id: String, +} + +impl __sdk::InModule for PuzzleClearRunGetInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_next_level_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_next_level_input_type.rs new file mode 100644 index 00000000..92fc2bfb --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_next_level_input_type.rs @@ -0,0 +1,18 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunNextLevelInput { + pub run_id: String, + pub owner_user_id: String, + pub client_action_id: String, + pub started_at_ms: i64, +} + +impl __sdk::InModule for PuzzleClearRunNextLevelInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_procedure_result_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_procedure_result_type.rs new file mode 100644 index 00000000..c9ed733c --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_procedure_result_type.rs @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_runtime_snapshot_type::PuzzleClearRuntimeSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunProcedureResult { + pub ok: bool, + pub run: Option, + pub error_message: Option, +} + +impl __sdk::InModule for PuzzleClearRunProcedureResult { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_retry_level_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_retry_level_input_type.rs new file mode 100644 index 00000000..1509038e --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_retry_level_input_type.rs @@ -0,0 +1,18 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunRetryLevelInput { + pub run_id: String, + pub owner_user_id: String, + pub client_action_id: String, + pub restarted_at_ms: i64, +} + +impl __sdk::InModule for PuzzleClearRunRetryLevelInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_start_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_start_input_type.rs new file mode 100644 index 00000000..8366824c --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_start_input_type.rs @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunStartInput { + pub run_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub client_event_id: String, + pub started_at_ms: i64, +} + +impl __sdk::InModule for PuzzleClearRunStartInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_swap_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_swap_input_type.rs new file mode 100644 index 00000000..119031fb --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_swap_input_type.rs @@ -0,0 +1,22 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunSwapInput { + pub run_id: String, + pub owner_user_id: String, + pub from_row: u32, + pub from_col: u32, + pub to_row: u32, + pub to_col: u32, + pub client_action_id: String, + pub swapped_at_ms: i64, +} + +impl __sdk::InModule for PuzzleClearRunSwapInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_time_up_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_time_up_input_type.rs new file mode 100644 index 00000000..4f3d2a50 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_run_time_up_input_type.rs @@ -0,0 +1,18 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRunTimeUpInput { + pub run_id: String, + pub owner_user_id: String, + pub client_action_id: String, + pub occurred_at_ms: i64, +} + +impl __sdk::InModule for PuzzleClearRunTimeUpInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_run_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_run_row_type.rs new file mode 100644 index 00000000..fa99c892 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_run_row_type.rs @@ -0,0 +1,83 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRuntimeRunRow { + pub run_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub status: String, + pub level_index: u32, + pub clears_done: u32, + pub snapshot_json: String, + pub started_at_ms: i64, + pub finished_at_ms: i64, + pub created_at: __sdk::Timestamp, + pub updated_at: __sdk::Timestamp, +} + +impl __sdk::InModule for PuzzleClearRuntimeRunRow { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `PuzzleClearRuntimeRunRow`. +/// +/// Provides typed access to columns for query building. +pub struct PuzzleClearRuntimeRunRowCols { + pub run_id: __sdk::__query_builder::Col, + pub owner_user_id: __sdk::__query_builder::Col, + pub profile_id: __sdk::__query_builder::Col, + pub status: __sdk::__query_builder::Col, + pub level_index: __sdk::__query_builder::Col, + pub clears_done: __sdk::__query_builder::Col, + pub snapshot_json: __sdk::__query_builder::Col, + pub started_at_ms: __sdk::__query_builder::Col, + pub finished_at_ms: __sdk::__query_builder::Col, + pub created_at: __sdk::__query_builder::Col, + pub updated_at: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for PuzzleClearRuntimeRunRow { + type Cols = PuzzleClearRuntimeRunRowCols; + fn cols(table_name: &'static str) -> Self::Cols { + PuzzleClearRuntimeRunRowCols { + run_id: __sdk::__query_builder::Col::new(table_name, "run_id"), + owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"), + profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"), + status: __sdk::__query_builder::Col::new(table_name, "status"), + level_index: __sdk::__query_builder::Col::new(table_name, "level_index"), + clears_done: __sdk::__query_builder::Col::new(table_name, "clears_done"), + snapshot_json: __sdk::__query_builder::Col::new(table_name, "snapshot_json"), + started_at_ms: __sdk::__query_builder::Col::new(table_name, "started_at_ms"), + finished_at_ms: __sdk::__query_builder::Col::new(table_name, "finished_at_ms"), + created_at: __sdk::__query_builder::Col::new(table_name, "created_at"), + updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"), + } + } +} + +/// Indexed column accessor struct for the table `PuzzleClearRuntimeRunRow`. +/// +/// Provides typed access to indexed columns for query building. +pub struct PuzzleClearRuntimeRunRowIxCols { + pub owner_user_id: __sdk::__query_builder::IxCol, + pub profile_id: __sdk::__query_builder::IxCol, + pub run_id: __sdk::__query_builder::IxCol, +} + +impl __sdk::__query_builder::HasIxCols for PuzzleClearRuntimeRunRow { + type IxCols = PuzzleClearRuntimeRunRowIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + PuzzleClearRuntimeRunRowIxCols { + owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"), + profile_id: __sdk::__query_builder::IxCol::new(table_name, "profile_id"), + run_id: __sdk::__query_builder::IxCol::new(table_name, "run_id"), + } + } +} + +impl __sdk::__query_builder::CanBeLookupTable for PuzzleClearRuntimeRunRow {} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_run_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_run_table.rs new file mode 100644 index 00000000..b83148fa --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_run_table.rs @@ -0,0 +1,162 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::puzzle_clear_runtime_run_row_type::PuzzleClearRuntimeRunRow; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `puzzle_clear_runtime_run`. +/// +/// Obtain a handle from the [`PuzzleClearRuntimeRunTableAccess::puzzle_clear_runtime_run`] method on [`super::RemoteTables`], +/// like `ctx.db.puzzle_clear_runtime_run()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_runtime_run().on_insert(...)`. +pub struct PuzzleClearRuntimeRunTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `puzzle_clear_runtime_run`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PuzzleClearRuntimeRunTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PuzzleClearRuntimeRunTableHandle`], which mediates access to the table `puzzle_clear_runtime_run`. + fn puzzle_clear_runtime_run(&self) -> PuzzleClearRuntimeRunTableHandle<'_>; +} + +impl PuzzleClearRuntimeRunTableAccess for super::RemoteTables { + fn puzzle_clear_runtime_run(&self) -> PuzzleClearRuntimeRunTableHandle<'_> { + PuzzleClearRuntimeRunTableHandle { + imp: self + .imp + .get_table::("puzzle_clear_runtime_run"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PuzzleClearRuntimeRunInsertCallbackId(__sdk::CallbackId); +pub struct PuzzleClearRuntimeRunDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PuzzleClearRuntimeRunTableHandle<'ctx> { + type Row = PuzzleClearRuntimeRunRow; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PuzzleClearRuntimeRunInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearRuntimeRunInsertCallbackId { + PuzzleClearRuntimeRunInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PuzzleClearRuntimeRunInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PuzzleClearRuntimeRunDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearRuntimeRunDeleteCallbackId { + PuzzleClearRuntimeRunDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PuzzleClearRuntimeRunDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct PuzzleClearRuntimeRunUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for PuzzleClearRuntimeRunTableHandle<'ctx> { + type UpdateCallbackId = PuzzleClearRuntimeRunUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> PuzzleClearRuntimeRunUpdateCallbackId { + PuzzleClearRuntimeRunUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: PuzzleClearRuntimeRunUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `run_id` unique index on the table `puzzle_clear_runtime_run`, +/// which allows point queries on the field of the same name +/// via the [`PuzzleClearRuntimeRunRunIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_runtime_run().run_id().find(...)`. +pub struct PuzzleClearRuntimeRunRunIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> PuzzleClearRuntimeRunTableHandle<'ctx> { + /// Get a handle on the `run_id` unique index on the table `puzzle_clear_runtime_run`. + pub fn run_id(&self) -> PuzzleClearRuntimeRunRunIdUnique<'ctx> { + PuzzleClearRuntimeRunRunIdUnique { + imp: self.imp.get_unique_constraint::("run_id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> PuzzleClearRuntimeRunRunIdUnique<'ctx> { + /// Find the subscribed row whose `run_id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &String) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = + client_cache.get_or_make_table::("puzzle_clear_runtime_run"); + _table.add_unique_constraint::("run_id", |row| &row.run_id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `PuzzleClearRuntimeRunRow`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait puzzle_clear_runtime_runQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `PuzzleClearRuntimeRunRow`. + fn puzzle_clear_runtime_run(&self) -> __sdk::__query_builder::Table; +} + +impl puzzle_clear_runtime_runQueryTableAccess for __sdk::QueryTableAccessor { + fn puzzle_clear_runtime_run(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("puzzle_clear_runtime_run") + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_snapshot_type.rs new file mode 100644 index 00000000..1ae78997 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_runtime_snapshot_type.rs @@ -0,0 +1,30 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_board_snapshot_type::PuzzleClearBoardSnapshot; +use super::puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearRuntimeSnapshot { + pub run_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub status: String, + pub level_index: u32, + pub clears_done: u32, + pub target_clears: u32, + pub level_duration_seconds: u32, + pub level_started_at_ms: u64, + pub board: PuzzleClearBoardSnapshot, + pub ready_columns: Vec>, + pub started_at_ms: u64, + pub finished_at_ms: Option, +} + +impl __sdk::InModule for PuzzleClearRuntimeSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_get_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_get_input_type.rs new file mode 100644 index 00000000..474a325f --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_get_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorkGetInput { + pub profile_id: String, + pub owner_user_id: String, +} + +impl __sdk::InModule for PuzzleClearWorkGetInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_procedure_result_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_procedure_result_type.rs new file mode 100644 index 00000000..451180a5 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_procedure_result_type.rs @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_work_snapshot_type::PuzzleClearWorkSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorkProcedureResult { + pub ok: bool, + pub work: Option, + pub error_message: Option, +} + +impl __sdk::InModule for PuzzleClearWorkProcedureResult { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_profile_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_profile_row_type.rs new file mode 100644 index 00000000..601eb56a --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_profile_row_type.rs @@ -0,0 +1,139 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorkProfileRow { + pub profile_id: String, + pub work_id: String, + pub owner_user_id: String, + pub source_session_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: String, + pub board_background_prompt: Option, + pub card_back_image_src: String, + pub atlas_asset_json: String, + pub pattern_groups_json: String, + pub card_assets_json: String, + pub cover_image_src: String, + pub generation_status: String, + pub publication_status: String, + pub play_count: u32, + pub updated_at: __sdk::Timestamp, + pub published_at: Option<__sdk::Timestamp>, + pub visible: bool, +} + +impl __sdk::InModule for PuzzleClearWorkProfileRow { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `PuzzleClearWorkProfileRow`. +/// +/// Provides typed access to columns for query building. +pub struct PuzzleClearWorkProfileRowCols { + pub profile_id: __sdk::__query_builder::Col, + pub work_id: __sdk::__query_builder::Col, + pub owner_user_id: __sdk::__query_builder::Col, + pub source_session_id: __sdk::__query_builder::Col, + pub author_display_name: __sdk::__query_builder::Col, + pub work_title: __sdk::__query_builder::Col, + pub work_description: __sdk::__query_builder::Col, + pub theme_prompt: __sdk::__query_builder::Col, + pub generate_board_background: __sdk::__query_builder::Col, + pub board_background_asset_json: __sdk::__query_builder::Col, + pub board_background_prompt: + __sdk::__query_builder::Col>, + pub card_back_image_src: __sdk::__query_builder::Col, + pub atlas_asset_json: __sdk::__query_builder::Col, + pub pattern_groups_json: __sdk::__query_builder::Col, + pub card_assets_json: __sdk::__query_builder::Col, + pub cover_image_src: __sdk::__query_builder::Col, + pub generation_status: __sdk::__query_builder::Col, + pub publication_status: __sdk::__query_builder::Col, + pub play_count: __sdk::__query_builder::Col, + pub updated_at: __sdk::__query_builder::Col, + pub published_at: + __sdk::__query_builder::Col>, + pub visible: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for PuzzleClearWorkProfileRow { + type Cols = PuzzleClearWorkProfileRowCols; + fn cols(table_name: &'static str) -> Self::Cols { + PuzzleClearWorkProfileRowCols { + profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"), + work_id: __sdk::__query_builder::Col::new(table_name, "work_id"), + owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"), + source_session_id: __sdk::__query_builder::Col::new(table_name, "source_session_id"), + author_display_name: __sdk::__query_builder::Col::new( + table_name, + "author_display_name", + ), + work_title: __sdk::__query_builder::Col::new(table_name, "work_title"), + work_description: __sdk::__query_builder::Col::new(table_name, "work_description"), + theme_prompt: __sdk::__query_builder::Col::new(table_name, "theme_prompt"), + generate_board_background: __sdk::__query_builder::Col::new( + table_name, + "generate_board_background", + ), + board_background_asset_json: __sdk::__query_builder::Col::new( + table_name, + "board_background_asset_json", + ), + board_background_prompt: __sdk::__query_builder::Col::new( + table_name, + "board_background_prompt", + ), + card_back_image_src: __sdk::__query_builder::Col::new( + table_name, + "card_back_image_src", + ), + atlas_asset_json: __sdk::__query_builder::Col::new(table_name, "atlas_asset_json"), + pattern_groups_json: __sdk::__query_builder::Col::new( + table_name, + "pattern_groups_json", + ), + card_assets_json: __sdk::__query_builder::Col::new(table_name, "card_assets_json"), + cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"), + generation_status: __sdk::__query_builder::Col::new(table_name, "generation_status"), + publication_status: __sdk::__query_builder::Col::new(table_name, "publication_status"), + play_count: __sdk::__query_builder::Col::new(table_name, "play_count"), + updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"), + published_at: __sdk::__query_builder::Col::new(table_name, "published_at"), + visible: __sdk::__query_builder::Col::new(table_name, "visible"), + } + } +} + +/// Indexed column accessor struct for the table `PuzzleClearWorkProfileRow`. +/// +/// Provides typed access to indexed columns for query building. +pub struct PuzzleClearWorkProfileRowIxCols { + pub owner_user_id: __sdk::__query_builder::IxCol, + pub profile_id: __sdk::__query_builder::IxCol, + pub publication_status: __sdk::__query_builder::IxCol, +} + +impl __sdk::__query_builder::HasIxCols for PuzzleClearWorkProfileRow { + type IxCols = PuzzleClearWorkProfileRowIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + PuzzleClearWorkProfileRowIxCols { + owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"), + profile_id: __sdk::__query_builder::IxCol::new(table_name, "profile_id"), + publication_status: __sdk::__query_builder::IxCol::new( + table_name, + "publication_status", + ), + } + } +} + +impl __sdk::__query_builder::CanBeLookupTable for PuzzleClearWorkProfileRow {} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_profile_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_profile_table.rs new file mode 100644 index 00000000..5baf3316 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_profile_table.rs @@ -0,0 +1,165 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::puzzle_clear_work_profile_row_type::PuzzleClearWorkProfileRow; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `puzzle_clear_work_profile`. +/// +/// Obtain a handle from the [`PuzzleClearWorkProfileTableAccess::puzzle_clear_work_profile`] method on [`super::RemoteTables`], +/// like `ctx.db.puzzle_clear_work_profile()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_work_profile().on_insert(...)`. +pub struct PuzzleClearWorkProfileTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `puzzle_clear_work_profile`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PuzzleClearWorkProfileTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PuzzleClearWorkProfileTableHandle`], which mediates access to the table `puzzle_clear_work_profile`. + fn puzzle_clear_work_profile(&self) -> PuzzleClearWorkProfileTableHandle<'_>; +} + +impl PuzzleClearWorkProfileTableAccess for super::RemoteTables { + fn puzzle_clear_work_profile(&self) -> PuzzleClearWorkProfileTableHandle<'_> { + PuzzleClearWorkProfileTableHandle { + imp: self + .imp + .get_table::("puzzle_clear_work_profile"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PuzzleClearWorkProfileInsertCallbackId(__sdk::CallbackId); +pub struct PuzzleClearWorkProfileDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PuzzleClearWorkProfileTableHandle<'ctx> { + type Row = PuzzleClearWorkProfileRow; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PuzzleClearWorkProfileInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearWorkProfileInsertCallbackId { + PuzzleClearWorkProfileInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PuzzleClearWorkProfileInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PuzzleClearWorkProfileDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PuzzleClearWorkProfileDeleteCallbackId { + PuzzleClearWorkProfileDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PuzzleClearWorkProfileDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct PuzzleClearWorkProfileUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for PuzzleClearWorkProfileTableHandle<'ctx> { + type UpdateCallbackId = PuzzleClearWorkProfileUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> PuzzleClearWorkProfileUpdateCallbackId { + PuzzleClearWorkProfileUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: PuzzleClearWorkProfileUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `profile_id` unique index on the table `puzzle_clear_work_profile`, +/// which allows point queries on the field of the same name +/// via the [`PuzzleClearWorkProfileProfileIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.puzzle_clear_work_profile().profile_id().find(...)`. +pub struct PuzzleClearWorkProfileProfileIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> PuzzleClearWorkProfileTableHandle<'ctx> { + /// Get a handle on the `profile_id` unique index on the table `puzzle_clear_work_profile`. + pub fn profile_id(&self) -> PuzzleClearWorkProfileProfileIdUnique<'ctx> { + PuzzleClearWorkProfileProfileIdUnique { + imp: self.imp.get_unique_constraint::("profile_id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> PuzzleClearWorkProfileProfileIdUnique<'ctx> { + /// Find the subscribed row whose `profile_id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &String) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = + client_cache.get_or_make_table::("puzzle_clear_work_profile"); + _table.add_unique_constraint::("profile_id", |row| &row.profile_id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `PuzzleClearWorkProfileRow`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait puzzle_clear_work_profileQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `PuzzleClearWorkProfileRow`. + fn puzzle_clear_work_profile(&self) + -> __sdk::__query_builder::Table; +} + +impl puzzle_clear_work_profileQueryTableAccess for __sdk::QueryTableAccessor { + fn puzzle_clear_work_profile( + &self, + ) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("puzzle_clear_work_profile") + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_publish_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_publish_input_type.rs new file mode 100644 index 00000000..27863d67 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_publish_input_type.rs @@ -0,0 +1,17 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorkPublishInput { + pub profile_id: String, + pub owner_user_id: String, + pub published_at_micros: i64, +} + +impl __sdk::InModule for PuzzleClearWorkPublishInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_snapshot_type.rs new file mode 100644 index 00000000..56df2029 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_snapshot_type.rs @@ -0,0 +1,40 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_card_asset_snapshot_type::PuzzleClearCardAssetSnapshot; +use super::puzzle_clear_image_asset_snapshot_type::PuzzleClearImageAssetSnapshot; +use super::puzzle_clear_pattern_group_snapshot_type::PuzzleClearPatternGroupSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorkSnapshot { + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub source_session_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + pub board_background_prompt: String, + pub card_back_image_src: Option, + pub atlas_asset: PuzzleClearImageAssetSnapshot, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub cover_image_src: Option, + pub publication_status: String, + pub publish_ready: bool, + pub play_count: u32, + pub generation_status: String, + pub updated_at_micros: i64, + pub published_at_micros: Option, +} + +impl __sdk::InModule for PuzzleClearWorkSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_update_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_update_input_type.rs new file mode 100644 index 00000000..6ba0d466 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_work_update_input_type.rs @@ -0,0 +1,23 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorkUpdateInput { + pub profile_id: String, + pub owner_user_id: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: Option, + pub board_background_prompt: String, + pub updated_at_micros: i64, +} + +impl __sdk::InModule for PuzzleClearWorkUpdateInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_works_list_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_works_list_input_type.rs new file mode 100644 index 00000000..9fc783b0 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_works_list_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorksListInput { + pub owner_user_id: String, + pub published_only: bool, +} + +impl __sdk::InModule for PuzzleClearWorksListInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_works_procedure_result_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_works_procedure_result_type.rs new file mode 100644 index 00000000..44260189 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_clear_works_procedure_result_type.rs @@ -0,0 +1,19 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_work_snapshot_type::PuzzleClearWorkSnapshot; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleClearWorksProcedureResult { + pub ok: bool, + pub items: Vec, + pub error_message: Option, +} + +impl __sdk::InModule for PuzzleClearWorksProcedureResult { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/query_analytics_metric_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/query_analytics_metric_procedure.rs index adc25bfa..7973f546 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/query_analytics_metric_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/query_analytics_metric_procedure.rs @@ -31,10 +31,10 @@ pub trait query_analytics_metric { input: AnalyticsMetricQueryInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl query_analytics_metric for super::RemoteProcedures { input: AnalyticsMetricQueryInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, AnalyticsMetricQueryProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_like_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_like_procedure.rs index 0429a9f7..536aa47d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_like_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_like_procedure.rs @@ -31,10 +31,10 @@ pub trait record_big_fish_like { input: BigFishWorkLikeRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_big_fish_like for super::RemoteProcedures { input: BigFishWorkLikeRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs index f4cfaa6b..8cf35b2b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs @@ -31,10 +31,10 @@ pub trait record_big_fish_play { input: BigFishPlayRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_big_fish_play for super::RemoteProcedures { input: BigFishPlayRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishWorksProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_like_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_like_procedure.rs index 6ac81dd9..1cd0aaad 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_like_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_like_procedure.rs @@ -31,10 +31,10 @@ pub trait record_custom_world_profile_like { input: CustomWorldProfileLikeRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_custom_world_profile_like for super::RemoteProcedures { input: CustomWorldProfileLikeRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_play_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_play_procedure.rs index f803e277..2534ee30 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_play_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_custom_world_profile_play_procedure.rs @@ -31,10 +31,10 @@ pub trait record_custom_world_profile_play { input: CustomWorldProfilePlayRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_custom_world_profile_play for super::RemoteProcedures { input: CustomWorldProfilePlayRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_daily_login_tracking_event_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_daily_login_tracking_event_and_return_procedure.rs index 9365d335..c131f3ba 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_daily_login_tracking_event_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_daily_login_tracking_event_and_return_procedure.rs @@ -34,10 +34,10 @@ pub trait record_daily_login_tracking_event_and_return { input: RuntimeProfileTaskCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl record_daily_login_tracking_event_and_return for super::RemoteProcedures { input: RuntimeProfileTaskCenterGetInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeTrackingEventProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_puzzle_work_like_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_puzzle_work_like_procedure.rs index 78dce7f8..fa55cf09 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_puzzle_work_like_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_puzzle_work_like_procedure.rs @@ -31,10 +31,10 @@ pub trait record_puzzle_work_like { input: PuzzleWorkLikeRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_puzzle_work_like for super::RemoteProcedures { input: PuzzleWorkLikeRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_event_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_event_and_return_procedure.rs index 01361ec7..c09132c0 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_event_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_event_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait record_tracking_event_and_return { input: RuntimeTrackingEventInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_tracking_event_and_return for super::RemoteProcedures { input: RuntimeTrackingEventInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeTrackingEventProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_events_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_events_and_return_procedure.rs index ba28d1a8..428e378f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_events_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_tracking_events_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait record_tracking_events_and_return { inputs: Vec, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_tracking_events_and_return for super::RemoteProcedures { inputs: Vec, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeTrackingEventBatchProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/record_visual_novel_runtime_event_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/record_visual_novel_runtime_event_procedure.rs index 8c8db759..39f3a431 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/record_visual_novel_runtime_event_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/record_visual_novel_runtime_event_procedure.rs @@ -31,10 +31,10 @@ pub trait record_visual_novel_runtime_event { input: VisualNovelRuntimeEventRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl record_visual_novel_runtime_event for super::RemoteProcedures { input: VisualNovelRuntimeEventRecordInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelRuntimeEventProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs index 44354acd..efebd26a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs @@ -31,10 +31,10 @@ pub trait redeem_profile_referral_invite_code { input: RuntimeReferralRedeemInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl redeem_profile_referral_invite_code for super::RemoteProcedures { input: RuntimeReferralRedeemInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeReferralRedeemProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs index 4d048a49..38fc64f5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs @@ -31,10 +31,10 @@ pub trait redeem_profile_reward_code { input: RuntimeProfileRewardCodeRedeemInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl redeem_profile_reward_code for super::RemoteProcedures { input: RuntimeProfileRewardCodeRedeemInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileRewardCodeRedeemProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs index fb86172c..a4bbd378 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait refund_profile_wallet_points_and_return { input: RuntimeProfileWalletAdjustmentInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl refund_profile_wallet_points_and_return for super::RemoteProcedures { input: RuntimeProfileWalletAdjustmentInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileWalletAdjustmentProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/remix_big_fish_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/remix_big_fish_work_procedure.rs index 7f58adb3..ff7d1486 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/remix_big_fish_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/remix_big_fish_work_procedure.rs @@ -31,10 +31,10 @@ pub trait remix_big_fish_work { input: BigFishWorkRemixInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl remix_big_fish_work for super::RemoteProcedures { input: BigFishWorkRemixInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/remix_custom_world_profile_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/remix_custom_world_profile_procedure.rs index 93f74383..8cd29b12 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/remix_custom_world_profile_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/remix_custom_world_profile_procedure.rs @@ -31,10 +31,10 @@ pub trait remix_custom_world_profile { input: CustomWorldProfileRemixInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl remix_custom_world_profile for super::RemoteProcedures { input: CustomWorldProfileRemixInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/remix_puzzle_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/remix_puzzle_work_procedure.rs index da91b334..d2548069 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/remix_puzzle_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/remix_puzzle_work_procedure.rs @@ -31,10 +31,10 @@ pub trait remix_puzzle_work { input: PuzzleWorkRemixInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl remix_puzzle_work for super::RemoteProcedures { input: PuzzleWorkRemixInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs index ac8aa07d..7d6fbfd1 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait resolve_combat_action_and_return { input: ResolveCombatActionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl resolve_combat_action_and_return for super::RemoteProcedures { input: ResolveCombatActionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, ResolveCombatActionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_reducer.rs index 41340e2f..8398db8c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_reducer.rs @@ -47,9 +47,11 @@ pub trait resolve_combat_action { &self, input: ResolveCombatActionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl resolve_combat_action for super::RemoteReducers { &self, input: ResolveCombatActionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ResolveCombatActionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs index ff4cca29..268483bf 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait resolve_npc_battle_interaction_and_return { input: ResolveNpcBattleInteractionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl resolve_npc_battle_interaction_and_return for super::RemoteProcedures { input: ResolveNpcBattleInteractionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, NpcBattleInteractionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs index aaba3d9c..4604fc30 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait resolve_npc_interaction_and_return { input: ResolveNpcInteractionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl resolve_npc_interaction_and_return for super::RemoteProcedures { input: ResolveNpcInteractionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, NpcInteractionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_reducer.rs index 52d213b5..352f5a93 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_reducer.rs @@ -47,9 +47,11 @@ pub trait resolve_npc_interaction { &self, input: ResolveNpcInteractionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl resolve_npc_interaction for super::RemoteReducers { &self, input: ResolveNpcInteractionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ResolveNpcInteractionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs index c1425649..65b1690e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait resolve_npc_social_action_and_return { input: ResolveNpcSocialActionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl resolve_npc_social_action_and_return for super::RemoteProcedures { input: ResolveNpcSocialActionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, NpcStateProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_reducer.rs index 28e5ce36..9b326931 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_reducer.rs @@ -47,9 +47,11 @@ pub trait resolve_npc_social_action { &self, input: ResolveNpcSocialActionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl resolve_npc_social_action for super::RemoteReducers { &self, input: ResolveNpcSocialActionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ResolveNpcSocialActionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs index a224c122..0b8f6bad 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait resolve_treasure_interaction_and_return { input: TreasureResolveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl resolve_treasure_interaction_and_return for super::RemoteProcedures { input: TreasureResolveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, TreasureRecordProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_reducer.rs index 942b377a..221ac39d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_reducer.rs @@ -47,9 +47,11 @@ pub trait resolve_treasure_interaction { &self, input: TreasureResolveInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl resolve_treasure_interaction for super::RemoteReducers { &self, input: TreasureResolveInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(ResolveTreasureInteractionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/restart_jump_hop_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/restart_jump_hop_run_procedure.rs index f88b059c..cde6daca 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/restart_jump_hop_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/restart_jump_hop_run_procedure.rs @@ -31,10 +31,10 @@ pub trait restart_jump_hop_run { input: JumpHopRunRestartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl restart_jump_hop_run for super::RemoteProcedures { input: JumpHopRunRestartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/restart_match_3_d_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/restart_match_3_d_run_procedure.rs index add954d4..76c74037 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/restart_match_3_d_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/restart_match_3_d_run_procedure.rs @@ -31,10 +31,10 @@ pub trait restart_match_3_d_run { input: Match3DRunRestartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl restart_match_3_d_run for super::RemoteProcedures { input: Match3DRunRestartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/restart_square_hole_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/restart_square_hole_run_procedure.rs index fe07f147..df72d4d3 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/restart_square_hole_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/restart_square_hole_run_procedure.rs @@ -31,10 +31,10 @@ pub trait restart_square_hole_run { input: SquareHoleRunRestartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl restart_square_hole_run for super::RemoteProcedures { input: SquareHoleRunRestartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs index 957c105f..73e6d668 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait resume_profile_save_archive_and_return { input: RuntimeProfileSaveArchiveResumeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl resume_profile_save_archive_and_return for super::RemoteProcedures { input: RuntimeProfileSaveArchiveResumeInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileSaveArchiveProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/retry_puzzle_clear_level_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/retry_puzzle_clear_level_run_procedure.rs new file mode 100644 index 00000000..4a7523c1 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/retry_puzzle_clear_level_run_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; +use super::puzzle_clear_run_retry_level_input_type::PuzzleClearRunRetryLevelInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct RetryPuzzleClearLevelRunArgs { + pub input: PuzzleClearRunRetryLevelInput, +} + +impl __sdk::InModule for RetryPuzzleClearLevelRunArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `retry_puzzle_clear_level_run`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait retry_puzzle_clear_level_run { + fn retry_puzzle_clear_level_run(&self, input: PuzzleClearRunRetryLevelInput) { + self.retry_puzzle_clear_level_run_then(input, |_, _| {}); + } + + fn retry_puzzle_clear_level_run_then( + &self, + input: PuzzleClearRunRetryLevelInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl retry_puzzle_clear_level_run for super::RemoteProcedures { + fn retry_puzzle_clear_level_run_then( + &self, + input: PuzzleClearRunRetryLevelInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearRunProcedureResult>( + "retry_puzzle_clear_level_run", + RetryPuzzleClearLevelRunArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs index feb5086e..fe032926 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs @@ -31,10 +31,10 @@ pub trait revoke_database_migration_operator { input: DatabaseMigrationRevokeOperatorInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl revoke_database_migration_operator for super::RemoteProcedures { input: DatabaseMigrationRevokeOperatorInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, DatabaseMigrationOperatorProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_form_draft_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_form_draft_procedure.rs index bd13cb5f..13aff9a3 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_form_draft_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_form_draft_procedure.rs @@ -31,10 +31,10 @@ pub trait save_puzzle_form_draft { input: PuzzleFormDraftSaveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl save_puzzle_form_draft for super::RemoteProcedures { input: PuzzleFormDraftSaveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs index 85d17456..870d6d51 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs @@ -31,10 +31,10 @@ pub trait save_puzzle_generated_images { input: PuzzleGeneratedImagesSaveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl save_puzzle_generated_images for super::RemoteProcedures { input: PuzzleGeneratedImagesSaveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_ui_background_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_ui_background_procedure.rs index 80fa6304..49750c17 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_ui_background_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_ui_background_procedure.rs @@ -31,10 +31,10 @@ pub trait save_puzzle_ui_background { input: PuzzleUiBackgroundSaveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl save_puzzle_ui_background for super::RemoteProcedures { input: PuzzleUiBackgroundSaveInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/seed_analytics_date_dimensions_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/seed_analytics_date_dimensions_reducer.rs index 6e2ac3ad..29d3b91d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/seed_analytics_date_dimensions_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/seed_analytics_date_dimensions_reducer.rs @@ -50,9 +50,11 @@ pub trait seed_analytics_date_dimensions { &self, input: AnalyticsDateDimensionSeedInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl seed_analytics_date_dimensions for super::RemoteReducers { &self, input: AnalyticsDateDimensionSeedInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(SeedAnalyticsDateDimensionsArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs index 9dde8aaa..fd4dd93c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs @@ -31,10 +31,10 @@ pub trait select_puzzle_cover_image { input: PuzzleSelectCoverImageInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl select_puzzle_cover_image for super::RemoteProcedures { input: PuzzleSelectCoverImageInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_message_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_message_table.rs index 9507df73..63d9fa6b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_message_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_message_table.rs @@ -153,7 +153,7 @@ pub trait square_hole_agent_messageQueryTableAccess { #[allow(non_snake_case)] /// Get a query builder for the table `SquareHoleAgentMessageRow`. fn square_hole_agent_message(&self) - -> __sdk::__query_builder::Table; + -> __sdk::__query_builder::Table; } impl square_hole_agent_messageQueryTableAccess for __sdk::QueryTableAccessor { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_session_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_session_table.rs index 1f8098b1..057e4469 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_session_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/square_hole_agent_session_table.rs @@ -153,7 +153,7 @@ pub trait square_hole_agent_sessionQueryTableAccess { #[allow(non_snake_case)] /// Get a query builder for the table `SquareHoleAgentSessionRow`. fn square_hole_agent_session(&self) - -> __sdk::__query_builder::Table; + -> __sdk::__query_builder::Table; } impl square_hole_agent_sessionQueryTableAccess for __sdk::QueryTableAccessor { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_reducer.rs index 5809736b..c5cc5256 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_reducer.rs @@ -47,9 +47,11 @@ pub trait start_ai_task { &self, input: AiTaskStartInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl start_ai_task for super::RemoteReducers { &self, input: AiTaskStartInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(StartAiTaskArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_stage_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_stage_reducer.rs index 1d7b7582..24ed5b3f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_stage_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_ai_task_stage_reducer.rs @@ -47,9 +47,11 @@ pub trait start_ai_task_stage { &self, input: AiTaskStageStartInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl start_ai_task_stage for super::RemoteReducers { &self, input: AiTaskStageStartInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(StartAiTaskStageArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_bark_battle_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_bark_battle_run_procedure.rs index a29ff5e9..1fdc1a09 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_bark_battle_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_bark_battle_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_bark_battle_run { input: BarkBattleRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_bark_battle_run for super::RemoteProcedures { input: BarkBattleRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_big_fish_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_big_fish_run_procedure.rs index 7f3713ab..6a149f03 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_big_fish_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_big_fish_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_big_fish_run { input: BigFishRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_big_fish_run for super::RemoteProcedures { input: BigFishRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_jump_hop_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_jump_hop_run_procedure.rs index c5da3298..7a52fc9f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_jump_hop_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_jump_hop_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_jump_hop_run { input: JumpHopRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_jump_hop_run for super::RemoteProcedures { input: JumpHopRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_match_3_d_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_match_3_d_run_procedure.rs index d4126af9..c0a8c8a9 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_match_3_d_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_match_3_d_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_match_3_d_run { input: Match3DRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_match_3_d_run for super::RemoteProcedures { input: Match3DRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_clear_runtime_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_clear_runtime_run_procedure.rs new file mode 100644 index 00000000..c9f6660c --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_clear_runtime_run_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; +use super::puzzle_clear_run_start_input_type::PuzzleClearRunStartInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct StartPuzzleClearRuntimeRunArgs { + pub input: PuzzleClearRunStartInput, +} + +impl __sdk::InModule for StartPuzzleClearRuntimeRunArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `start_puzzle_clear_runtime_run`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait start_puzzle_clear_runtime_run { + fn start_puzzle_clear_runtime_run(&self, input: PuzzleClearRunStartInput) { + self.start_puzzle_clear_runtime_run_then(input, |_, _| {}); + } + + fn start_puzzle_clear_runtime_run_then( + &self, + input: PuzzleClearRunStartInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl start_puzzle_clear_runtime_run for super::RemoteProcedures { + fn start_puzzle_clear_runtime_run_then( + &self, + input: PuzzleClearRunStartInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearRunProcedureResult>( + "start_puzzle_clear_runtime_run", + StartPuzzleClearRuntimeRunArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs index b6baeb83..c3d6d457 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_puzzle_run { input: PuzzleRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_puzzle_run for super::RemoteProcedures { input: PuzzleRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_square_hole_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_square_hole_run_procedure.rs index d85e730b..bda9eddd 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_square_hole_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_square_hole_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_square_hole_run { input: SquareHoleRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_square_hole_run for super::RemoteProcedures { input: SquareHoleRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_visual_novel_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_visual_novel_run_procedure.rs index 98f9cfa0..fabc598a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_visual_novel_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_visual_novel_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_visual_novel_run { input: VisualNovelRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_visual_novel_run for super::RemoteProcedures { input: VisualNovelRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/start_wooden_fish_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/start_wooden_fish_run_procedure.rs index 65fa87c2..daf6358e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/start_wooden_fish_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/start_wooden_fish_run_procedure.rs @@ -31,10 +31,10 @@ pub trait start_wooden_fish_run { input: WoodenFishRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl start_wooden_fish_run for super::RemoteProcedures { input: WoodenFishRunStartInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/stop_match_3_d_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/stop_match_3_d_run_procedure.rs index f87a63d5..630e7a98 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/stop_match_3_d_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/stop_match_3_d_run_procedure.rs @@ -31,10 +31,10 @@ pub trait stop_match_3_d_run { input: Match3DRunStopInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl stop_match_3_d_run for super::RemoteProcedures { input: Match3DRunStopInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/stop_square_hole_run_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/stop_square_hole_run_procedure.rs index 076a3af8..90702e09 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/stop_square_hole_run_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/stop_square_hole_run_procedure.rs @@ -31,10 +31,10 @@ pub trait stop_square_hole_run { input: SquareHoleRunStopInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl stop_square_hole_run for super::RemoteProcedures { input: SquareHoleRunStopInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_input_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_input_procedure.rs index 8ef444c5..e5bbe6b0 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_input_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_input_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_big_fish_input { input: BigFishInputSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_big_fish_input for super::RemoteProcedures { input: BigFishInputSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs index 0dbc0118..307d7755 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_big_fish_message { input: BigFishMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_big_fish_message for super::RemoteProcedures { input: BigFishMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BigFishSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs index c5e8def1..5debac19 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_custom_world_agent_message { input: CustomWorldAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_custom_world_agent_message for super::RemoteProcedures { input: CustomWorldAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentOperationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_match_3_d_agent_message_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_match_3_d_agent_message_procedure.rs index 1ceceaf5..c323ecda 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_match_3_d_agent_message_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_match_3_d_agent_message_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_match_3_d_agent_message { input: Match3DAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_match_3_d_agent_message for super::RemoteProcedures { input: Match3DAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_profile_feedback_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_profile_feedback_and_return_procedure.rs index 3534df7d..208a35a9 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_profile_feedback_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_profile_feedback_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_profile_feedback_and_return { input: RuntimeProfileFeedbackSubmissionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_profile_feedback_and_return for super::RemoteProcedures { input: RuntimeProfileFeedbackSubmissionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeProfileFeedbackSubmissionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs index b5b2b090..0d9e94e5 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_puzzle_agent_message { input: PuzzleAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_puzzle_agent_message for super::RemoteProcedures { input: PuzzleAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs index 7df24564..9f72a916 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_puzzle_leaderboard_entry { input: PuzzleLeaderboardSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_puzzle_leaderboard_entry for super::RemoteProcedures { input: PuzzleLeaderboardSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_square_hole_agent_message_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_square_hole_agent_message_procedure.rs index e740fe61..bbdbe51d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_square_hole_agent_message_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_square_hole_agent_message_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_square_hole_agent_message { input: SquareHoleAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_square_hole_agent_message for super::RemoteProcedures { input: SquareHoleAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/submit_visual_novel_agent_message_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/submit_visual_novel_agent_message_procedure.rs index 0df949bd..5e9c93bb 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/submit_visual_novel_agent_message_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/submit_visual_novel_agent_message_procedure.rs @@ -31,10 +31,10 @@ pub trait submit_visual_novel_agent_message { input: VisualNovelAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl submit_visual_novel_agent_message for super::RemoteProcedures { input: VisualNovelAgentMessageSubmitInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelAgentSessionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_clear_cards_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_clear_cards_procedure.rs new file mode 100644 index 00000000..d527f9ed --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_clear_cards_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_run_procedure_result_type::PuzzleClearRunProcedureResult; +use super::puzzle_clear_run_swap_input_type::PuzzleClearRunSwapInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct SwapPuzzleClearCardsArgs { + pub input: PuzzleClearRunSwapInput, +} + +impl __sdk::InModule for SwapPuzzleClearCardsArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `swap_puzzle_clear_cards`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait swap_puzzle_clear_cards { + fn swap_puzzle_clear_cards(&self, input: PuzzleClearRunSwapInput) { + self.swap_puzzle_clear_cards_then(input, |_, _| {}); + } + + fn swap_puzzle_clear_cards_then( + &self, + input: PuzzleClearRunSwapInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl swap_puzzle_clear_cards for super::RemoteProcedures { + fn swap_puzzle_clear_cards_then( + &self, + input: PuzzleClearRunSwapInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearRunProcedureResult>( + "swap_puzzle_clear_cards", + SwapPuzzleClearCardsArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs index 9c6a1337..f5835607 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs @@ -31,10 +31,10 @@ pub trait swap_puzzle_pieces { input: PuzzleRunSwapInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl swap_puzzle_pieces for super::RemoteProcedures { input: PuzzleRunSwapInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/turn_in_quest_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/turn_in_quest_reducer.rs index 4306a47f..08727f04 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/turn_in_quest_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/turn_in_quest_reducer.rs @@ -47,9 +47,11 @@ pub trait turn_in_quest { &self, input: QuestTurnInInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl turn_in_quest for super::RemoteReducers { &self, input: QuestTurnInInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(TurnInQuestArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs index b87880a7..31acccce 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait unpublish_custom_world_profile_and_return { input: CustomWorldProfileUnpublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl unpublish_custom_world_profile_and_return for super::RemoteProcedures { input: CustomWorldProfileUnpublishInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_reducer.rs index 05274f62..51af927f 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_reducer.rs @@ -50,9 +50,11 @@ pub trait unpublish_custom_world_profile { &self, input: CustomWorldProfileUnpublishInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl unpublish_custom_world_profile for super::RemoteReducers { &self, input: CustomWorldProfileUnpublishInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(UnpublishCustomWorldProfileArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_bark_battle_draft_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_bark_battle_draft_config_procedure.rs index 545ca3f8..bd9fc421 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_bark_battle_draft_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_bark_battle_draft_config_procedure.rs @@ -31,10 +31,10 @@ pub trait update_bark_battle_draft_config { input: BarkBattleDraftConfigUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_bark_battle_draft_config for super::RemoteProcedures { input: BarkBattleDraftConfigUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, BarkBattleProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_jump_hop_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_jump_hop_work_procedure.rs index c5f246bf..9186048b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_jump_hop_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_jump_hop_work_procedure.rs @@ -31,10 +31,10 @@ pub trait update_jump_hop_work { input: JumpHopWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_jump_hop_work for super::RemoteProcedures { input: JumpHopWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, JumpHopWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_match_3_d_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_match_3_d_work_procedure.rs index ea2ee9b4..cdc42782 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_match_3_d_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_match_3_d_work_procedure.rs @@ -31,10 +31,10 @@ pub trait update_match_3_d_work { input: Match3DWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_match_3_d_work for super::RemoteProcedures { input: Match3DWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, Match3DWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_clear_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_clear_work_procedure.rs new file mode 100644 index 00000000..5f4b592a --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_clear_work_procedure.rs @@ -0,0 +1,59 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +use super::puzzle_clear_work_procedure_result_type::PuzzleClearWorkProcedureResult; +use super::puzzle_clear_work_update_input_type::PuzzleClearWorkUpdateInput; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct UpdatePuzzleClearWorkArgs { + pub input: PuzzleClearWorkUpdateInput, +} + +impl __sdk::InModule for UpdatePuzzleClearWorkArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `update_puzzle_clear_work`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait update_puzzle_clear_work { + fn update_puzzle_clear_work(&self, input: PuzzleClearWorkUpdateInput) { + self.update_puzzle_clear_work_then(input, |_, _| {}); + } + + fn update_puzzle_clear_work_then( + &self, + input: PuzzleClearWorkUpdateInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ); +} + +impl update_puzzle_clear_work for super::RemoteProcedures { + fn update_puzzle_clear_work_then( + &self, + input: PuzzleClearWorkUpdateInput, + + __callback: impl FnOnce( + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, PuzzleClearWorkProcedureResult>( + "update_puzzle_clear_work", + UpdatePuzzleClearWorkArgs { input }, + __callback, + ); + } +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_run_pause_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_run_pause_procedure.rs index 1679b5ec..3388380c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_run_pause_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_run_pause_procedure.rs @@ -31,10 +31,10 @@ pub trait update_puzzle_run_pause { input: PuzzleRunPauseInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_puzzle_run_pause for super::RemoteProcedures { input: PuzzleRunPauseInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs index 6710b4da..80571241 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs @@ -31,10 +31,10 @@ pub trait update_puzzle_work { input: PuzzleWorkUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_puzzle_work for super::RemoteProcedures { input: PuzzleWorkUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_square_hole_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_square_hole_work_procedure.rs index 16786527..914b3f05 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_square_hole_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_square_hole_work_procedure.rs @@ -31,10 +31,10 @@ pub trait update_square_hole_work { input: SquareHoleWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_square_hole_work for super::RemoteProcedures { input: SquareHoleWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, SquareHoleWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_visual_novel_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_visual_novel_work_procedure.rs index b6e61e33..bb0ccc4e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_visual_novel_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_visual_novel_work_procedure.rs @@ -31,10 +31,10 @@ pub trait update_visual_novel_work { input: VisualNovelWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_visual_novel_work for super::RemoteProcedures { input: VisualNovelWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/update_wooden_fish_work_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/update_wooden_fish_work_procedure.rs index 6b7c03d8..31785275 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/update_wooden_fish_work_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/update_wooden_fish_work_procedure.rs @@ -31,10 +31,10 @@ pub trait update_wooden_fish_work { input: WoodenFishWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl update_wooden_fish_work for super::RemoteProcedures { input: WoodenFishWorkUpdateInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, WoodenFishWorkProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs index abc39bfe..be3ff473 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_chapter_progression_and_return { input: ChapterProgressionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_chapter_progression_and_return for super::RemoteProcedures { input: ChapterProgressionInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, ChapterProgressionProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_reducer.rs index 0cb4bb7a..de37e994 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_reducer.rs @@ -47,9 +47,11 @@ pub trait upsert_chapter_progression { &self, input: ChapterProgressionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl upsert_chapter_progression for super::RemoteReducers { &self, input: ChapterProgressionInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(UpsertChapterProgressionArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_event_banners_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_event_banners_config_procedure.rs index aea3d28e..58e38d90 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_event_banners_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_event_banners_config_procedure.rs @@ -34,10 +34,10 @@ pub trait upsert_creation_entry_event_banners_config { input: CreationEntryEventBannersAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl upsert_creation_entry_event_banners_config for super::RemoteProcedures { input: CreationEntryEventBannersAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CreationEntryConfigProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_type_config_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_type_config_procedure.rs index 32df13cb..98f1f7fb 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_type_config_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_creation_entry_type_config_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_creation_entry_type_config { input: CreationEntryTypeAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_creation_entry_type_config for super::RemoteProcedures { input: CreationEntryTypeAdminUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CreationEntryConfigProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs index 554b95b2..6d8e39a2 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs @@ -34,10 +34,10 @@ pub trait upsert_custom_world_agent_operation_progress { input: CustomWorldAgentOperationProgressInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -47,10 +47,10 @@ impl upsert_custom_world_agent_operation_progress for super::RemoteProcedures { input: CustomWorldAgentOperationProgressInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldAgentOperationProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs index 343e1807..a761ab95 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_custom_world_profile_and_return { input: CustomWorldProfileUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_custom_world_profile_and_return for super::RemoteProcedures { input: CustomWorldProfileUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, CustomWorldLibraryMutationResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_reducer.rs index e343b491..91ca2652 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_reducer.rs @@ -50,9 +50,11 @@ pub trait upsert_custom_world_profile { &self, input: CustomWorldProfileUpsertInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -61,9 +63,11 @@ impl upsert_custom_world_profile for super::RemoteReducers { &self, input: CustomWorldProfileUpsertInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(UpsertCustomWorldProfileArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs index b7f3a28d..91ba0d0e 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_npc_state_and_return { input: NpcStateUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_npc_state_and_return for super::RemoteProcedures { input: NpcStateUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, NpcStateProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_reducer.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_reducer.rs index f363770d..afbe61f8 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_reducer.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_reducer.rs @@ -47,9 +47,11 @@ pub trait upsert_npc_state { &self, input: NpcStateUpsertInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()>; } @@ -58,9 +60,11 @@ impl upsert_npc_state for super::RemoteReducers { &self, input: NpcStateUpsertInput, - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, + callback: impl FnOnce( + &super::ReducerEventContext, + Result, __sdk::InternalError>, + ) + Send + + 'static, ) -> __sdk::Result<()> { self.imp .invoke_reducer_with_callback(UpsertNpcStateArgs { input }, callback) diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs index 614a6d05..36e5f464 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_platform_browse_history_and_return { input: RuntimeBrowseHistorySyncInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_platform_browse_history_and_return for super::RemoteProcedures { input: RuntimeBrowseHistorySyncInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeBrowseHistoryProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs index 119eab70..f8fa0351 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_runtime_setting_and_return { input: RuntimeSettingUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_runtime_setting_and_return for super::RemoteProcedures { input: RuntimeSettingUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeSettingProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs index eceae785..fe0746ba 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_runtime_snapshot_and_return { input: RuntimeSnapshotUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_runtime_snapshot_and_return for super::RemoteProcedures { input: RuntimeSnapshotUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, RuntimeSnapshotProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/upsert_visual_novel_run_snapshot_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/upsert_visual_novel_run_snapshot_procedure.rs index 35722b1e..8cc89e85 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/upsert_visual_novel_run_snapshot_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/upsert_visual_novel_run_snapshot_procedure.rs @@ -31,10 +31,10 @@ pub trait upsert_visual_novel_run_snapshot { input: VisualNovelRunSnapshotUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl upsert_visual_novel_run_snapshot for super::RemoteProcedures { input: VisualNovelRunSnapshotUpsertInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, VisualNovelRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/use_puzzle_runtime_prop_procedure.rs b/server-rs/crates/spacetime-client/src/module_bindings/use_puzzle_runtime_prop_procedure.rs index 45bd3a73..1e2dbe13 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/use_puzzle_runtime_prop_procedure.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/use_puzzle_runtime_prop_procedure.rs @@ -31,10 +31,10 @@ pub trait use_puzzle_runtime_prop { input: PuzzleRunPropInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ); } @@ -44,10 +44,10 @@ impl use_puzzle_runtime_prop for super::RemoteProcedures { input: PuzzleRunPropInput, __callback: impl FnOnce( - &super::ProcedureEventContext, - Result, - ) + Send - + 'static, + &super::ProcedureEventContext, + Result, + ) + Send + + 'static, ) { self.imp .invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>( diff --git a/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_gallery_view_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_gallery_view_table.rs index 1b393127..a1f70563 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_gallery_view_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_gallery_view_table.rs @@ -105,7 +105,7 @@ pub trait visual_novel_gallery_viewQueryTableAccess { #[allow(non_snake_case)] /// Get a query builder for the table `VisualNovelGalleryViewRow`. fn visual_novel_gallery_view(&self) - -> __sdk::__query_builder::Table; + -> __sdk::__query_builder::Table; } impl visual_novel_gallery_viewQueryTableAccess for __sdk::QueryTableAccessor { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_work_profile_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_work_profile_table.rs index 55371a46..24682e11 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_work_profile_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/visual_novel_work_profile_table.rs @@ -153,7 +153,7 @@ pub trait visual_novel_work_profileQueryTableAccess { #[allow(non_snake_case)] /// Get a query builder for the table `VisualNovelWorkProfileRow`. fn visual_novel_work_profile(&self) - -> __sdk::__query_builder::Table; + -> __sdk::__query_builder::Table; } impl visual_novel_work_profileQueryTableAccess for __sdk::QueryTableAccessor { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_agent_session_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_agent_session_table.rs index 2b9e7d58..68f2e484 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_agent_session_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_agent_session_table.rs @@ -153,7 +153,7 @@ pub trait wooden_fish_agent_sessionQueryTableAccess { #[allow(non_snake_case)] /// Get a query builder for the table `WoodenFishAgentSessionRow`. fn wooden_fish_agent_session(&self) - -> __sdk::__query_builder::Table; + -> __sdk::__query_builder::Table; } impl wooden_fish_agent_sessionQueryTableAccess for __sdk::QueryTableAccessor { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_delete_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_delete_input_type.rs new file mode 100644 index 00000000..7aebaec6 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_delete_input_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct WoodenFishWorkDeleteInput { + pub profile_id: String, + pub owner_user_id: String, +} + +impl __sdk::InModule for WoodenFishWorkDeleteInput { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/puzzle_clear.rs b/server-rs/crates/spacetime-client/src/puzzle_clear.rs new file mode 100644 index 00000000..8d1dc0ed --- /dev/null +++ b/server-rs/crates/spacetime-client/src/puzzle_clear.rs @@ -0,0 +1,1021 @@ +use super::*; +use crate::mapper::{ + map_puzzle_clear_agent_session_procedure_result, map_puzzle_clear_gallery_card_view_row, + map_puzzle_clear_run_procedure_result, map_puzzle_clear_work_procedure_result, + map_puzzle_clear_works_procedure_result, +}; +use module_puzzle_clear::{PUZZLE_CLEAR_PROFILE_ID_PREFIX, PUZZLE_CLEAR_RUN_ID_PREFIX}; +use shared_contracts::puzzle_clear::{ + PuzzleClearActionRequest, PuzzleClearActionResponse, PuzzleClearActionType, + PuzzleClearGenerationStatus, PuzzleClearImageAsset, PuzzleClearNextLevelRequest, + PuzzleClearPatternGroup, PuzzleClearRetryLevelRequest, PuzzleClearRuntimeSnapshotResponse, + PuzzleClearSessionSnapshotResponse, PuzzleClearStartRunRequest, PuzzleClearSwapRequest, + PuzzleClearTimeUpRequest, PuzzleClearWorkProfileResponse, PuzzleClearWorkSummaryResponse, +}; +use shared_kernel::build_prefixed_uuid_id; + +const PUZZLE_CLEAR_TEMPLATE_ID: &str = "puzzle-clear"; +const PUZZLE_CLEAR_TEMPLATE_NAME: &str = "拼消消"; +const PUZZLE_CLEAR_ATLAS_CELL_SIZE: u32 = 128; +const PUZZLE_CLEAR_ASSET_OBJECT_ID_PREFIX: &str = "assetobj_"; + +impl SpacetimeClient { + pub async fn create_puzzle_clear_session( + &self, + session: PuzzleClearSessionSnapshotResponse, + ) -> Result { + let draft = session.draft.clone().ok_or_else(|| { + SpacetimeClientError::validation_failed("puzzle-clear session 缺少 draft") + })?; + let procedure_input = PuzzleClearAgentSessionCreateInput { + session_id: session.session_id, + owner_user_id: session.owner_user_id, + work_title: draft.work_title, + work_description: draft.work_description, + theme_prompt: draft.theme_prompt, + generate_board_background: draft.generate_board_background, + board_background_asset_json: draft + .board_background_asset + .as_ref() + .map(json_string) + .transpose()?, + board_background_prompt: draft.board_background_prompt, + created_at_micros: current_unix_micros(), + }; + + self.call_after_connect( + "create_puzzle_clear_agent_session", + move |connection, sender| { + connection + .procedures() + .create_puzzle_clear_agent_session_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) + .await + } + + pub async fn get_puzzle_clear_session( + &self, + session_id: String, + owner_user_id: String, + ) -> Result { + let procedure_input = PuzzleClearAgentSessionGetInput { + session_id, + owner_user_id, + }; + + self.call_after_connect( + "get_puzzle_clear_agent_session", + move |connection, sender| { + connection.procedures().get_puzzle_clear_agent_session_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) + .await + } + + pub async fn execute_puzzle_clear_action( + &self, + session_id: String, + owner_user_id: String, + author_display_name: String, + payload: PuzzleClearActionRequest, + ) -> Result { + let current = self + .get_puzzle_clear_session(session_id.clone(), owner_user_id.clone()) + .await?; + let (procedure, _) = build_puzzle_clear_action_plan( + ¤t, + &owner_user_id, + &author_display_name, + &payload, + current_unix_micros(), + )?; + let (session, work) = match procedure { + PuzzleClearActionProcedure::Compile(input) => { + let profile_id = input.profile_id.clone(); + let session = self.compile_puzzle_clear_draft(input).await?; + let work = self + .get_puzzle_clear_work_profile(profile_id, owner_user_id) + .await + .ok(); + (session, work) + } + PuzzleClearActionProcedure::Update(input) => { + let work = self.update_puzzle_clear_work(input).await?; + let session = apply_puzzle_clear_work_to_session(current, &work); + (session, Some(work)) + } + }; + + Ok(PuzzleClearActionResponse { + action_type: payload.action_type, + session, + work, + }) + } + + pub async fn compile_puzzle_clear_draft( + &self, + procedure_input: PuzzleClearDraftCompileInput, + ) -> Result { + self.call_after_connect("compile_puzzle_clear_draft", move |connection, sender| { + connection.procedures().compile_puzzle_clear_draft_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn mark_puzzle_clear_generation_failed( + &self, + session_id: String, + owner_user_id: String, + author_display_name: String, + payload: PuzzleClearActionRequest, + ) -> Result { + let current = self + .get_puzzle_clear_session(session_id, owner_user_id.clone()) + .await?; + let procedure_input = build_failed_compile_input( + ¤t, + &owner_user_id, + &author_display_name, + &payload, + current_unix_micros(), + )?; + self.compile_puzzle_clear_draft(procedure_input).await + } + + pub async fn get_puzzle_clear_work_profile( + &self, + profile_id: String, + owner_user_id: String, + ) -> Result { + let procedure_input = PuzzleClearWorkGetInput { + profile_id, + owner_user_id, + }; + + self.call_after_connect( + "get_puzzle_clear_work_profile", + move |connection, sender| { + connection.procedures().get_puzzle_clear_work_profile_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_work_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) + .await + } + + pub async fn update_puzzle_clear_work( + &self, + procedure_input: PuzzleClearWorkUpdateInput, + ) -> Result { + self.call_after_connect("update_puzzle_clear_work", move |connection, sender| { + connection.procedures().update_puzzle_clear_work_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_work_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn publish_puzzle_clear_work( + &self, + profile_id: String, + owner_user_id: String, + ) -> Result { + let procedure_input = PuzzleClearWorkPublishInput { + profile_id, + owner_user_id, + published_at_micros: current_unix_micros(), + }; + + self.call_after_connect("publish_puzzle_clear_work", move |connection, sender| { + connection.procedures().publish_puzzle_clear_work_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_work_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn list_puzzle_clear_works( + &self, + owner_user_id: String, + ) -> Result, SpacetimeClientError> { + let procedure_input = PuzzleClearWorksListInput { + owner_user_id, + published_only: false, + }; + + self.call_after_connect("list_puzzle_clear_works", move |connection, sender| { + connection.procedures().list_puzzle_clear_works_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_works_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn get_puzzle_clear_runtime_work( + &self, + profile_id: String, + ) -> Result { + let work = self + .get_puzzle_clear_work_profile(profile_id, String::new()) + .await?; + validate_puzzle_clear_runtime_ready(&work)?; + Ok(work) + } + + pub async fn start_puzzle_clear_run( + &self, + payload: PuzzleClearStartRunRequest, + owner_user_id: String, + ) -> Result { + let profile_id = payload.profile_id; + let work = self + .get_puzzle_clear_work_profile(profile_id.clone(), String::new()) + .await?; + validate_puzzle_clear_runtime_ready(&work)?; + let run_id = build_prefixed_uuid_id(PUZZLE_CLEAR_RUN_ID_PREFIX); + let procedure_input = PuzzleClearRunStartInput { + client_event_id: format!("{run_id}:start"), + run_id, + owner_user_id, + profile_id, + started_at_ms: current_unix_micros().div_euclid(1000), + }; + self.start_puzzle_clear_run_with_input(procedure_input) + .await + } + + pub async fn start_puzzle_clear_run_with_input( + &self, + procedure_input: PuzzleClearRunStartInput, + ) -> Result { + self.call_after_connect( + "start_puzzle_clear_runtime_run", + move |connection, sender| { + connection.procedures().start_puzzle_clear_runtime_run_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_run_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) + .await + } + + pub async fn get_puzzle_clear_run( + &self, + run_id: String, + owner_user_id: String, + ) -> Result { + let procedure_input = PuzzleClearRunGetInput { + run_id, + owner_user_id, + }; + + self.call_after_connect("get_puzzle_clear_runtime_run", move |connection, sender| { + connection.procedures().get_puzzle_clear_runtime_run_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_run_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn swap_puzzle_clear_cards( + &self, + run_id: String, + owner_user_id: String, + payload: PuzzleClearSwapRequest, + ) -> Result { + let procedure_input = PuzzleClearRunSwapInput { + run_id, + owner_user_id, + from_row: payload.from_row, + from_col: payload.from_col, + to_row: payload.to_row, + to_col: payload.to_col, + client_action_id: payload.client_action_id, + swapped_at_ms: current_unix_micros().div_euclid(1000), + }; + + self.call_after_connect("swap_puzzle_clear_cards", move |connection, sender| { + connection.procedures().swap_puzzle_clear_cards_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_run_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn retry_puzzle_clear_level( + &self, + run_id: String, + owner_user_id: String, + payload: PuzzleClearRetryLevelRequest, + ) -> Result { + let procedure_input = PuzzleClearRunRetryLevelInput { + run_id, + owner_user_id, + client_action_id: payload.client_action_id, + restarted_at_ms: current_unix_micros().div_euclid(1000), + }; + + self.call_after_connect("retry_puzzle_clear_level_run", move |connection, sender| { + connection.procedures().retry_puzzle_clear_level_run_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_run_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + + pub async fn advance_puzzle_clear_next_level( + &self, + run_id: String, + owner_user_id: String, + payload: PuzzleClearNextLevelRequest, + ) -> Result { + let procedure_input = PuzzleClearRunNextLevelInput { + run_id, + owner_user_id, + client_action_id: payload.client_action_id, + started_at_ms: current_unix_micros().div_euclid(1000), + }; + + self.call_after_connect( + "advance_puzzle_clear_next_level", + move |connection, sender| { + connection + .procedures() + .advance_puzzle_clear_next_level_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_run_procedure_result); + send_once(&sender, mapped); + }); + }, + ) + .await + } + + pub async fn mark_puzzle_clear_level_time_up( + &self, + run_id: String, + owner_user_id: String, + payload: PuzzleClearTimeUpRequest, + ) -> Result { + let procedure_input = PuzzleClearRunTimeUpInput { + run_id, + owner_user_id, + client_action_id: payload.client_action_id, + occurred_at_ms: current_unix_micros().div_euclid(1000), + }; + + self.call_after_connect( + "mark_puzzle_clear_level_time_up", + move |connection, sender| { + connection + .procedures() + .mark_puzzle_clear_level_time_up_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_clear_run_procedure_result); + send_once(&sender, mapped); + }); + }, + ) + .await + } + + pub async fn list_puzzle_clear_gallery( + &self, + ) -> Result, SpacetimeClientError> { + self.read_after_connect("list_puzzle_clear_gallery", move |connection| { + let mut items = connection + .db() + .puzzle_clear_gallery_card_view() + .iter() + .collect::>(); + items.sort_by(|left, right| { + right + .updated_at_micros + .cmp(&left.updated_at_micros) + .then_with(|| left.profile_id.cmp(&right.profile_id)) + }); + Ok(items + .into_iter() + .map(map_puzzle_clear_gallery_card_view_row) + .collect()) + }) + .await + } +} + +enum PuzzleClearActionProcedure { + Compile(PuzzleClearDraftCompileInput), + Update(PuzzleClearWorkUpdateInput), +} + +#[derive(Clone, Copy)] +enum PuzzleClearDraftMergeScope { + CompileDraft, + RegenerateAtlas, + UpdateWorkMeta, + UpdateBoardBackground, +} + +fn build_puzzle_clear_action_plan( + current: &PuzzleClearSessionSnapshotResponse, + owner_user_id: &str, + author_display_name: &str, + payload: &PuzzleClearActionRequest, + now_micros: i64, +) -> Result<(PuzzleClearActionProcedure, PuzzleClearDraftResponse), SpacetimeClientError> { + let scope = match payload.action_type { + PuzzleClearActionType::CompileDraft => PuzzleClearDraftMergeScope::CompileDraft, + PuzzleClearActionType::RegenerateAtlas => PuzzleClearDraftMergeScope::RegenerateAtlas, + PuzzleClearActionType::UpdateWorkMeta => PuzzleClearDraftMergeScope::UpdateWorkMeta, + PuzzleClearActionType::UpdateBoardBackground => { + PuzzleClearDraftMergeScope::UpdateBoardBackground + } + }; + let mut draft = merge_action_into_draft(current.draft.clone(), payload, scope)?; + let profile_id = resolve_puzzle_clear_profile_id( + &draft, + &payload.action_type, + payload.profile_id.as_deref(), + )?; + draft.profile_id = Some(profile_id.clone()); + let procedure = + match payload.action_type { + PuzzleClearActionType::CompileDraft | PuzzleClearActionType::RegenerateAtlas => { + PuzzleClearActionProcedure::Compile(build_compile_input( + current, + owner_user_id, + author_display_name, + &profile_id, + &mut draft, + now_micros, + )?) + } + PuzzleClearActionType::UpdateWorkMeta + | PuzzleClearActionType::UpdateBoardBackground => PuzzleClearActionProcedure::Update( + build_update_input(owner_user_id, &profile_id, &draft, now_micros)?, + ), + }; + + Ok((procedure, draft)) +} + +fn merge_action_into_draft( + draft: Option, + payload: &PuzzleClearActionRequest, + scope: PuzzleClearDraftMergeScope, +) -> Result { + let mut draft = draft.unwrap_or_else(default_draft); + if matches!( + scope, + PuzzleClearDraftMergeScope::CompileDraft + | PuzzleClearDraftMergeScope::UpdateWorkMeta + | PuzzleClearDraftMergeScope::RegenerateAtlas + ) { + if let Some(value) = payload + .work_title + .as_ref() + .and_then(|value| non_empty_str(value)) + { + draft.work_title = value; + } + if let Some(value) = payload.work_description.as_ref() { + draft.work_description = value.trim().to_string(); + } + if let Some(value) = payload + .theme_prompt + .as_ref() + .and_then(|value| non_empty_str(value)) + { + draft.theme_prompt = value; + } + if let Some(value) = payload + .board_background_prompt + .as_ref() + .and_then(|value| non_empty_str(value)) + { + draft.board_background_prompt = value; + } + } + if matches!( + scope, + PuzzleClearDraftMergeScope::CompileDraft + | PuzzleClearDraftMergeScope::UpdateBoardBackground + | PuzzleClearDraftMergeScope::RegenerateAtlas + ) { + if let Some(value) = payload.generate_board_background { + draft.generate_board_background = value; + } + if payload.board_background_asset.is_some() { + draft.board_background_asset = payload.board_background_asset.clone(); + } + } + if matches!( + scope, + PuzzleClearDraftMergeScope::CompileDraft | PuzzleClearDraftMergeScope::RegenerateAtlas + ) { + if let Some(asset) = payload.atlas_asset.clone() { + draft.atlas_asset = Some(asset); + } + if let Some(groups) = payload.pattern_groups.clone() { + draft.pattern_groups = groups; + } + if let Some(cards) = payload.card_assets.clone() { + draft.card_assets = cards; + } + if draft.pattern_groups.is_empty() { + draft.pattern_groups = default_pattern_groups(); + } + draft.generation_status = PuzzleClearGenerationStatus::Ready; + } + + if draft.work_title.trim().is_empty() || draft.theme_prompt.trim().is_empty() { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear 草稿需要标题和主题词", + )); + } + Ok(draft) +} + +fn build_compile_input( + current: &PuzzleClearSessionSnapshotResponse, + owner_user_id: &str, + author_display_name: &str, + profile_id: &str, + draft: &mut PuzzleClearDraftResponse, + now_micros: i64, +) -> Result { + if draft.pattern_groups.is_empty() { + draft.pattern_groups = default_pattern_groups(); + } + let atlas_asset = ensure_real_puzzle_clear_atlas_asset(draft.atlas_asset.as_ref())?; + ensure_real_puzzle_clear_card_assets(&draft.card_assets)?; + + Ok(PuzzleClearDraftCompileInput { + session_id: current.session_id.clone(), + owner_user_id: owner_user_id.to_string(), + profile_id: profile_id.to_string(), + author_display_name: non_empty_str(author_display_name) + .unwrap_or_else(|| "拼消消玩家".to_string()), + work_title: draft.work_title.clone(), + work_description: draft.work_description.clone(), + theme_prompt: draft.theme_prompt.clone(), + board_background_prompt: draft.board_background_prompt.clone(), + generate_board_background: draft.generate_board_background, + board_background_asset_json: draft + .board_background_asset + .as_ref() + .map(json_string) + .transpose()?, + atlas_asset_json: Some(json_string(atlas_asset)?), + pattern_groups_json: Some(json_string(&draft.pattern_groups)?), + card_assets_json: Some(json_string(&draft.card_assets)?), + generation_status: Some("ready".to_string()), + compiled_at_micros: now_micros, + }) +} + +fn build_failed_compile_input( + current: &PuzzleClearSessionSnapshotResponse, + owner_user_id: &str, + author_display_name: &str, + payload: &PuzzleClearActionRequest, + now_micros: i64, +) -> Result { + let mut draft = current.draft.clone().unwrap_or_else(default_draft); + if let Some(value) = payload + .work_title + .as_ref() + .and_then(|value| non_empty_str(value)) + { + draft.work_title = value; + } + if let Some(value) = payload.work_description.as_ref() { + draft.work_description = value.trim().to_string(); + } + if let Some(value) = payload + .theme_prompt + .as_ref() + .and_then(|value| non_empty_str(value)) + { + draft.theme_prompt = value; + } + if let Some(value) = payload + .board_background_prompt + .as_ref() + .and_then(|value| non_empty_str(value)) + { + draft.board_background_prompt = value; + } + if let Some(value) = payload.generate_board_background { + draft.generate_board_background = value; + } + if let Some(asset) = payload.board_background_asset.clone() { + draft.board_background_asset = Some(asset); + } + if let Some(profile_id) = payload + .profile_id + .as_ref() + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + { + draft.profile_id = Some(profile_id.to_string()); + } + draft.generation_status = PuzzleClearGenerationStatus::Failed; + let profile_id = resolve_puzzle_clear_profile_id( + &draft, + &PuzzleClearActionType::CompileDraft, + draft.profile_id.as_deref(), + )?; + draft.profile_id = Some(profile_id.clone()); + + Ok(PuzzleClearDraftCompileInput { + session_id: current.session_id.clone(), + owner_user_id: owner_user_id.to_string(), + profile_id, + author_display_name: non_empty_str(author_display_name) + .unwrap_or_else(|| "拼消消玩家".to_string()), + work_title: non_empty_str(draft.work_title.as_str()) + .unwrap_or_else(|| PUZZLE_CLEAR_TEMPLATE_NAME.to_string()), + work_description: draft.work_description.trim().to_string(), + theme_prompt: non_empty_str(draft.theme_prompt.as_str()) + .unwrap_or_else(|| PUZZLE_CLEAR_TEMPLATE_NAME.to_string()), + board_background_prompt: draft.board_background_prompt.clone(), + generate_board_background: draft.generate_board_background, + board_background_asset_json: draft + .board_background_asset + .as_ref() + .map(json_string) + .transpose()?, + atlas_asset_json: None, + pattern_groups_json: None, + card_assets_json: None, + generation_status: Some("failed".to_string()), + compiled_at_micros: now_micros, + }) +} + +fn build_update_input( + owner_user_id: &str, + profile_id: &str, + draft: &PuzzleClearDraftResponse, + now_micros: i64, +) -> Result { + Ok(PuzzleClearWorkUpdateInput { + profile_id: profile_id.to_string(), + owner_user_id: owner_user_id.to_string(), + work_title: draft.work_title.clone(), + work_description: draft.work_description.clone(), + theme_prompt: draft.theme_prompt.clone(), + board_background_prompt: draft.board_background_prompt.clone(), + generate_board_background: draft.generate_board_background, + board_background_asset_json: draft + .board_background_asset + .as_ref() + .map(json_string) + .transpose()?, + updated_at_micros: now_micros, + }) +} + +fn resolve_puzzle_clear_profile_id( + draft: &PuzzleClearDraftResponse, + action_type: &PuzzleClearActionType, + payload_profile_id: Option<&str>, +) -> Result { + if let Some(profile_id) = payload_profile_id + .map(str::trim) + .filter(|value| !value.is_empty()) + { + return Ok(profile_id.to_string()); + } + if let Some(profile_id) = draft + .profile_id + .as_ref() + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + { + return Ok(profile_id.to_string()); + } + if matches!(action_type, PuzzleClearActionType::CompileDraft) { + return Ok(build_prefixed_uuid_id(PUZZLE_CLEAR_PROFILE_ID_PREFIX)); + } + Err(SpacetimeClientError::validation_failed( + "puzzle-clear action 需要先完成 compile-draft", + )) +} + +fn apply_puzzle_clear_work_to_session( + mut session: PuzzleClearSessionSnapshotResponse, + work: &PuzzleClearWorkProfileResponse, +) -> PuzzleClearSessionSnapshotResponse { + session.status = work.draft.generation_status.clone(); + session.draft = Some(work.draft.clone()); + session.updated_at = work.summary.updated_at.clone(); + session +} + +fn validate_puzzle_clear_runtime_ready( + work: &PuzzleClearWorkProfileResponse, +) -> Result<(), SpacetimeClientError> { + if work.summary.publication_status != "published" { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear runtime 只能启动已发布作品", + )); + } + if work.summary.generation_status != PuzzleClearGenerationStatus::Ready { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear runtime 需要 ready 状态作品", + )); + } + if work.card_assets.is_empty() || work.pattern_groups.is_empty() { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear runtime 缺少切片卡牌资产", + )); + } + Ok(()) +} + +fn ensure_real_puzzle_clear_atlas_asset( + asset: Option<&PuzzleClearImageAsset>, +) -> Result<&PuzzleClearImageAsset, SpacetimeClientError> { + let Some(asset) = asset else { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear atlas 缺少真实生成资产", + )); + }; + if !is_real_puzzle_clear_asset( + asset.asset_object_id.as_str(), + asset.image_object_key.as_str(), + asset.image_src.as_str(), + ) { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear atlas 缺少真实生成资产", + )); + } + Ok(asset) +} + +fn ensure_real_puzzle_clear_card_assets( + assets: &[PuzzleClearCardAsset], +) -> Result<(), SpacetimeClientError> { + if assets.is_empty() { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear card assets 缺少真实生成资产", + )); + } + if assets.iter().any(|asset| { + !is_real_puzzle_clear_asset( + asset.asset_object_id.as_str(), + asset.image_object_key.as_str(), + asset.image_src.as_str(), + ) + }) { + return Err(SpacetimeClientError::validation_failed( + "puzzle-clear card assets 缺少真实生成资产", + )); + } + Ok(()) +} + +fn is_real_puzzle_clear_asset( + asset_object_id: &str, + image_object_key: &str, + image_src: &str, +) -> bool { + asset_object_id.starts_with(PUZZLE_CLEAR_ASSET_OBJECT_ID_PREFIX) + && image_object_key.starts_with("generated-puzzle-clear-assets/") + && image_src.starts_with("/generated-puzzle-clear-assets/") +} + +fn default_draft() -> PuzzleClearDraftResponse { + PuzzleClearDraftResponse { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: None, + work_title: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + work_description: String::new(), + theme_prompt: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + board_background_prompt: String::new(), + generate_board_background: true, + board_background_asset: None, + card_back_image_src: Some("/creation-type-references/puzzle.webp".to_string()), + atlas_asset: None, + pattern_groups: Vec::new(), + card_assets: Vec::new(), + generation_status: PuzzleClearGenerationStatus::Draft, + } +} + +fn default_pattern_groups() -> Vec { + module_puzzle_clear::plan_puzzle_clear_pattern_groups(PUZZLE_CLEAR_ATLAS_CELL_SIZE) + .unwrap_or_default() + .into_iter() + .map(|group| PuzzleClearPatternGroup { + group_id: group.group_id, + shape: group.shape.as_str().to_string(), + width: group.width, + height: group.height, + atlas_x: group.atlas_x, + atlas_y: group.atlas_y, + atlas_width: group.atlas_width, + atlas_height: group.atlas_height, + }) + .collect() +} + +fn non_empty_str(value: &str) -> Option { + let value = value.trim(); + if value.is_empty() { + None + } else { + Some(value.to_string()) + } +} + +fn json_string(value: &T) -> Result { + serde_json::to_string(value).map_err(SpacetimeClientError::validation_failed) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SESSION_ID: &str = "puzzle-clear-session-test"; + const OWNER_USER_ID: &str = "user-test"; + const NOW_MICROS: i64 = 1_780_000_000_000_000; + + #[test] + fn puzzle_clear_compile_requires_real_atlas_assets_from_api_server() { + let session = session_with_draft(draft_without_assets()); + let payload = action(PuzzleClearActionType::CompileDraft); + + let error = match build_puzzle_clear_action_plan( + &session, + OWNER_USER_ID, + "拼消消玩家", + &payload, + NOW_MICROS, + ) { + Ok(_) => panic!("compile-draft should not synthesize placeholder atlas assets"), + Err(error) => error, + }; + + assert!(error.to_string().contains("atlas")); + assert!(error.to_string().contains("真实生成资产")); + } + + #[test] + fn puzzle_clear_failure_writeback_does_not_require_generated_assets() { + let session = session_with_draft(draft_without_assets()); + + let input = build_failed_compile_input( + &session, + OWNER_USER_ID, + "拼消消玩家", + &PuzzleClearActionRequest { + action_type: PuzzleClearActionType::CompileDraft, + profile_id: None, + work_title: None, + work_description: Some("VectorEngine 素材 atlas 生成失败".to_string()), + theme_prompt: None, + board_background_prompt: None, + generate_board_background: None, + board_background_asset: None, + atlas_asset: None, + pattern_groups: None, + card_assets: None, + }, + NOW_MICROS, + ) + .expect("failed writeback input should be buildable without assets"); + + assert_eq!(input.session_id, SESSION_ID); + assert_eq!(input.owner_user_id, OWNER_USER_ID); + assert_eq!(input.generation_status.as_deref(), Some("failed")); + assert!(input.atlas_asset_json.is_none()); + assert!(input.pattern_groups_json.is_none()); + assert!(input.card_assets_json.is_none()); + assert_eq!(input.work_title, "水果拼消消"); + assert_eq!(input.theme_prompt, "水果"); + assert_eq!(input.work_description, "VectorEngine 素材 atlas 生成失败"); + } + + fn session_with_draft(draft: PuzzleClearDraftResponse) -> PuzzleClearSessionSnapshotResponse { + PuzzleClearSessionSnapshotResponse { + session_id: SESSION_ID.to_string(), + owner_user_id: OWNER_USER_ID.to_string(), + status: draft.generation_status.clone(), + draft: Some(draft), + created_at: "2026-05-30T00:00:00Z".to_string(), + updated_at: "2026-05-30T00:00:00Z".to_string(), + } + } + + fn draft_without_assets() -> PuzzleClearDraftResponse { + PuzzleClearDraftResponse { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: None, + work_title: "水果拼消消".to_string(), + work_description: String::new(), + theme_prompt: "水果".to_string(), + board_background_prompt: String::new(), + generate_board_background: false, + board_background_asset: None, + card_back_image_src: Some("/creation-type-references/puzzle.webp".to_string()), + atlas_asset: None, + pattern_groups: Vec::new(), + card_assets: Vec::new(), + generation_status: PuzzleClearGenerationStatus::Draft, + } + } + + fn action(action_type: PuzzleClearActionType) -> PuzzleClearActionRequest { + PuzzleClearActionRequest { + action_type, + profile_id: None, + work_title: None, + work_description: None, + theme_prompt: None, + board_background_prompt: None, + generate_board_background: None, + board_background_asset: None, + atlas_asset: None, + pattern_groups: None, + card_assets: None, + } + } +} diff --git a/server-rs/crates/spacetime-client/src/wooden_fish.rs b/server-rs/crates/spacetime-client/src/wooden_fish.rs index d09f5238..ddc7f867 100644 --- a/server-rs/crates/spacetime-client/src/wooden_fish.rs +++ b/server-rs/crates/spacetime-client/src/wooden_fish.rs @@ -259,6 +259,30 @@ impl SpacetimeClient { .await } + pub async fn delete_wooden_fish_work( + &self, + profile_id: String, + owner_user_id: String, + ) -> Result, SpacetimeClientError> { + let procedure_input = WoodenFishWorkDeleteInput { + profile_id, + owner_user_id, + }; + + self.call_after_connect("delete_wooden_fish_work", move |connection, sender| { + connection.procedures().delete_wooden_fish_work_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_wooden_fish_works_procedure_result); + send_once(&sender, mapped); + }, + ); + }) + .await + } + pub async fn get_wooden_fish_runtime_work( &self, profile_id: String, diff --git a/server-rs/crates/spacetime-module/Cargo.toml b/server-rs/crates/spacetime-module/Cargo.toml index 20104957..d2bcf450 100644 --- a/server-rs/crates/spacetime-module/Cargo.toml +++ b/server-rs/crates/spacetime-module/Cargo.toml @@ -24,6 +24,7 @@ module-wooden-fish = { workspace = true, features = ["spacetime-types"] } module-match3d = { workspace = true } module-npc = { workspace = true, features = ["spacetime-types"] } module-puzzle = { workspace = true, features = ["spacetime-types"] } +module-puzzle-clear = { workspace = true, features = ["spacetime-types"] } module-progression = { workspace = true, features = ["spacetime-types"] } module-quest = { workspace = true, features = ["spacetime-types"] } module-runtime = { workspace = true, features = ["spacetime-types"] } diff --git a/server-rs/crates/spacetime-module/src/auth/procedures.rs b/server-rs/crates/spacetime-module/src/auth/procedures.rs index 5f93eb01..b11acc80 100644 --- a/server-rs/crates/spacetime-module/src/auth/procedures.rs +++ b/server-rs/crates/spacetime-module/src/auth/procedures.rs @@ -454,13 +454,29 @@ fn export_auth_store_snapshot_from_tables_tx( .meta_id() .find(&AUTH_STORE_PROJECTION_META_ID.to_string()) .map(|row| row.updated_at.to_micros_since_unix_epoch()); + let snapshot = build_auth_store_snapshot_from_rows(users, identities, sessions)?; + if let Some(updated_at_micros) = updated_at_micros { + upsert_auth_store_snapshot_rows(ctx, &snapshot, updated_at_micros)?; + } + let snapshot_json = serde_json::to_string_pretty(&snapshot) + .map_err(|error| format!("序列化认证快照失败:{error}"))?; + + Ok(AuthStoreSnapshotRecord { + snapshot_json: Some(snapshot_json), + updated_at_micros, + }) +} + +fn build_auth_store_snapshot_from_rows( + users: Vec, + identities: Vec, + sessions: Vec, +) -> Result { let valid_user_ids = users .iter() .map(|user| user.user_id.clone()) .collect::>(); - let mut phone_identity_by_user_id = std::collections::HashMap::new(); - let mut phone_to_user_id = std::collections::HashMap::new(); let mut wechat_identity_by_provider_uid = std::collections::HashMap::new(); let mut user_id_by_provider_union_id = std::collections::HashMap::new(); @@ -475,7 +491,6 @@ fn export_auth_store_snapshot_from_tables_tx( .phone_e164 .clone() .unwrap_or_else(|| identity.provider_uid.clone()); - phone_to_user_id.insert(phone_number.clone(), identity.user_id.clone()); phone_identity_by_user_id.insert(identity.user_id, phone_number); } "wechat" => { @@ -498,6 +513,7 @@ fn export_auth_store_snapshot_from_tables_tx( } let mut next_user_id = 1_u64; + let mut phone_to_user_id = std::collections::HashMap::new(); let mut users_by_username = std::collections::HashMap::new(); for user in users { if let Some(numeric_id) = user @@ -507,6 +523,13 @@ fn export_auth_store_snapshot_from_tables_tx( { next_user_id = next_user_id.max(numeric_id.saturating_add(1)); } + let phone_number = user + .phone_number_e164 + .clone() + .or_else(|| phone_identity_by_user_id.remove(&user.user_id)); + if let Some(phone_number) = phone_number.clone() { + phone_to_user_id.insert(phone_number, user.user_id.clone()); + } let auth_user = AuthUserSnapshot { id: user.user_id.clone(), public_user_code: user.public_user_code, @@ -527,9 +550,7 @@ fn export_auth_store_snapshot_from_tables_tx( user: auth_user, password_hash: user.password_hash, password_login_enabled: user.password_login_enabled, - phone_number: user - .phone_number_e164 - .or_else(|| phone_identity_by_user_id.remove(&user.user_id)), + phone_number, }, ); } @@ -566,7 +587,7 @@ fn export_auth_store_snapshot_from_tables_tx( ); } - let snapshot = PersistentAuthStoreSnapshot { + Ok(PersistentAuthStoreSnapshot { next_user_id, users_by_username, phone_to_user_id, @@ -574,16 +595,6 @@ fn export_auth_store_snapshot_from_tables_tx( session_id_by_refresh_token_hash, wechat_identity_by_provider_uid, user_id_by_provider_union_id, - }; - if let Some(updated_at_micros) = updated_at_micros { - upsert_auth_store_snapshot_rows(ctx, &snapshot, updated_at_micros)?; - } - let snapshot_json = serde_json::to_string_pretty(&snapshot) - .map_err(|error| format!("序列化认证快照失败:{error}"))?; - - Ok(AuthStoreSnapshotRecord { - snapshot_json: Some(snapshot_json), - updated_at_micros, }) } @@ -721,4 +732,47 @@ mod tests { auth_store_snapshot_row_ids(&after) ); } + + #[test] + fn auth_export_ignores_phone_identity_without_user_account() { + let live_user = UserAccount { + user_id: "user_live".to_string(), + public_user_code: "SY-00000001".to_string(), + username: "phone_live".to_string(), + display_name: "测试玩家".to_string(), + avatar_url: None, + phone_number_masked: Some("138****8000".to_string()), + phone_number_e164: Some("+8613800008000".to_string()), + login_method: "phone".to_string(), + binding_status: "active".to_string(), + wechat_bound: false, + password_hash: "hash-live".to_string(), + password_login_enabled: true, + token_version: 1, + user_tags: Some(vec![]), + }; + let orphan_identity = AuthIdentity { + identity_id: "authi_phone_orphan".to_string(), + user_id: "user_deleted".to_string(), + provider: "phone".to_string(), + provider_uid: "+8613900009999".to_string(), + provider_union_id: None, + phone_e164: Some("+8613900009999".to_string()), + display_name: None, + avatar_url: None, + }; + + let snapshot = + build_auth_store_snapshot_from_rows(vec![live_user], vec![orphan_identity], vec![]) + .expect("auth rows should export"); + + assert_eq!( + snapshot.phone_to_user_id, + std::collections::HashMap::from([( + "+8613800008000".to_string(), + "user_live".to_string() + )]) + ); + assert!(!snapshot.phone_to_user_id.contains_key("+8613900009999")); + } } diff --git a/server-rs/crates/spacetime-module/src/bark_battle.rs b/server-rs/crates/spacetime-module/src/bark_battle.rs index 2a19b32a..0bf4c65a 100644 --- a/server-rs/crates/spacetime-module/src/bark_battle.rs +++ b/server-rs/crates/spacetime-module/src/bark_battle.rs @@ -75,6 +75,17 @@ pub fn publish_bark_battle_work( } } +#[spacetimedb::procedure] +pub fn delete_bark_battle_work( + ctx: &mut ProcedureContext, + input: BarkBattleWorkDeleteInput, +) -> BarkBattleProcedureResult { + match ctx.try_with_tx(|tx| delete_bark_battle_work_tx(tx, input.clone())) { + Ok(()) => bark_battle_empty_result(), + Err(error) => bark_battle_error_result(error), + } +} + #[spacetimedb::procedure] pub fn get_bark_battle_runtime_config( ctx: &mut ProcedureContext, @@ -286,6 +297,111 @@ fn publish_bark_battle_work_tx( Ok(runtime_config_snapshot(&published)) } +fn delete_bark_battle_work_tx( + ctx: &ReducerContext, + input: BarkBattleWorkDeleteInput, +) -> Result<(), String> { + require_non_empty(&input.work_id, "bark_battle work_id")?; + require_non_empty(&input.owner_user_id, "bark_battle owner_user_id")?; + let drafts = ctx + .db + .bark_battle_draft_config() + .by_bark_battle_draft_work_id() + .filter(input.work_id.as_str()) + .collect::>(); + let published = ctx + .db + .bark_battle_published_config() + .work_id() + .find(&input.work_id); + if drafts.is_empty() && published.is_none() { + return Err("bark_battle work 不存在".to_string()); + } + if drafts + .iter() + .any(|draft| draft.owner_user_id != input.owner_user_id) + || published + .as_ref() + .is_some_and(|row| row.owner_user_id != input.owner_user_id) + { + return Err("bark_battle work owner 不匹配".to_string()); + } + + for draft in drafts { + ctx.db + .bark_battle_draft_config() + .draft_id() + .delete(&draft.draft_id); + } + if let Some(published) = published { + ctx.db + .bark_battle_published_config() + .work_id() + .delete(&published.work_id); + } + for run in ctx + .db + .bark_battle_runtime_run() + .by_bark_battle_run_work_id() + .filter(input.work_id.as_str()) + .collect::>() + { + ctx.db + .bark_battle_runtime_run() + .run_id() + .delete(&run.run_id); + } + for score in ctx + .db + .bark_battle_score_record() + .by_bark_battle_score_work_id() + .filter(input.work_id.as_str()) + .collect::>() + { + ctx.db + .bark_battle_score_record() + .score_id() + .delete(&score.score_id); + } + for entry in ctx + .db + .bark_battle_leaderboard_entry() + .iter() + .filter(|entry| entry.work_id == input.work_id) + .collect::>() + { + ctx.db + .bark_battle_leaderboard_entry() + .leaderboard_entry_id() + .delete(&entry.leaderboard_entry_id); + } + for personal_best in ctx + .db + .bark_battle_personal_best_projection() + .by_bark_battle_personal_best_work_id() + .filter(input.work_id.as_str()) + .collect::>() + { + ctx.db + .bark_battle_personal_best_projection() + .personal_best_id() + .delete(&personal_best.personal_best_id); + } + if ctx + .db + .bark_battle_work_stats_projection() + .work_id() + .find(&input.work_id) + .is_some() + { + ctx.db + .bark_battle_work_stats_projection() + .work_id() + .delete(&input.work_id); + } + Ok(()) +} + fn get_bark_battle_runtime_config_tx( ctx: &ReducerContext, input: BarkBattleRuntimeConfigGetInput, @@ -763,6 +879,16 @@ fn bark_battle_run_result(run: BarkBattleRunSnapshot) -> BarkBattleProcedureResu } } +fn bark_battle_empty_result() -> BarkBattleProcedureResult { + BarkBattleProcedureResult { + ok: true, + draft_config: None, + runtime_config: None, + run: None, + error_message: None, + } +} + fn bark_battle_error_result(error: String) -> BarkBattleProcedureResult { BarkBattleProcedureResult { ok: false, @@ -1043,6 +1169,17 @@ mod tests { assert!(result.ok); } + #[test] + fn bark_battle_delete_input_carries_owner_and_work() { + let input = BarkBattleWorkDeleteInput { + work_id: "BB-12345678".to_string(), + owner_user_id: "user-1".to_string(), + }; + + assert_eq!(input.work_id, "BB-12345678"); + assert_eq!(input.owner_user_id, "user-1"); + } + #[test] fn validates_light_editor_config_before_publish() { assert_eq!( diff --git a/server-rs/crates/spacetime-module/src/bark_battle/types.rs b/server-rs/crates/spacetime-module/src/bark_battle/types.rs index 4dfda5b9..008aca53 100644 --- a/server-rs/crates/spacetime-module/src/bark_battle/types.rs +++ b/server-rs/crates/spacetime-module/src/bark_battle/types.rs @@ -53,6 +53,12 @@ pub struct BarkBattleWorkPublishInput { pub published_at_micros: i64, } +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct BarkBattleWorkDeleteInput { + pub work_id: String, + pub owner_user_id: String, +} + #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct BarkBattleRuntimeConfigGetInput { pub work_id: String, diff --git a/server-rs/crates/spacetime-module/src/jump_hop.rs b/server-rs/crates/spacetime-module/src/jump_hop.rs index 0ff204da..cc600047 100644 --- a/server-rs/crates/spacetime-module/src/jump_hop.rs +++ b/server-rs/crates/spacetime-module/src/jump_hop.rs @@ -204,6 +204,25 @@ pub fn list_jump_hop_works( } } +#[spacetimedb::procedure] +pub fn delete_jump_hop_work( + ctx: &mut ProcedureContext, + input: JumpHopWorkDeleteInput, +) -> JumpHopWorksProcedureResult { + match ctx.try_with_tx(|tx| delete_jump_hop_work_tx(tx, input.clone())) { + Ok(items) => JumpHopWorksProcedureResult { + ok: true, + items, + error_message: None, + }, + Err(message) => JumpHopWorksProcedureResult { + ok: false, + items: Vec::new(), + error_message: Some(message), + }, + } +} + #[spacetimedb::procedure] pub fn start_jump_hop_run( ctx: &mut ProcedureContext, @@ -573,6 +592,56 @@ fn list_jump_hop_works_tx( .collect() } +fn delete_jump_hop_work_tx( + ctx: &ReducerContext, + input: JumpHopWorkDeleteInput, +) -> Result, String> { + let work = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?; + ctx.db + .jump_hop_work_profile() + .profile_id() + .delete(&work.profile_id); + if !work.source_session_id.trim().is_empty() { + if let Some(session) = ctx + .db + .jump_hop_agent_session() + .session_id() + .find(&work.source_session_id) + .filter(|session| session.owner_user_id == input.owner_user_id) + { + ctx.db + .jump_hop_agent_session() + .session_id() + .delete(&session.session_id); + } + } + for run in ctx + .db + .jump_hop_runtime_run() + .by_jump_hop_run_profile_id() + .filter(input.profile_id.as_str()) + .collect::>() + { + ctx.db.jump_hop_runtime_run().run_id().delete(&run.run_id); + } + for event in ctx + .db + .jump_hop_event() + .by_jump_hop_event_profile_id() + .filter(input.profile_id.as_str()) + .collect::>() + { + ctx.db.jump_hop_event().event_id().delete(&event.event_id); + } + list_jump_hop_works_tx( + ctx, + JumpHopWorksListInput { + owner_user_id: input.owner_user_id, + published_only: false, + }, + ) +} + fn start_jump_hop_run_tx( ctx: &ReducerContext, input: JumpHopRunStartInput, @@ -1505,3 +1574,19 @@ mod tests { )); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn jump_hop_delete_input_carries_owner_and_profile() { + let input = JumpHopWorkDeleteInput { + profile_id: "jump-hop-profile-1".to_string(), + owner_user_id: "user-1".to_string(), + }; + + assert_eq!(input.profile_id, "jump-hop-profile-1"); + assert_eq!(input.owner_user_id, "user-1"); + } +} diff --git a/server-rs/crates/spacetime-module/src/jump_hop/types.rs b/server-rs/crates/spacetime-module/src/jump_hop/types.rs index 45441a3d..42c1d12b 100644 --- a/server-rs/crates/spacetime-module/src/jump_hop/types.rs +++ b/server-rs/crates/spacetime-module/src/jump_hop/types.rs @@ -82,6 +82,12 @@ pub struct JumpHopWorkPublishInput { pub published_at_micros: i64, } +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct JumpHopWorkDeleteInput { + pub profile_id: String, + pub owner_user_id: String, +} + #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct JumpHopWorksListInput { pub owner_user_id: String, diff --git a/server-rs/crates/spacetime-module/src/lib.rs b/server-rs/crates/spacetime-module/src/lib.rs index 2955c517..c54055ce 100644 --- a/server-rs/crates/spacetime-module/src/lib.rs +++ b/server-rs/crates/spacetime-module/src/lib.rs @@ -11,6 +11,7 @@ pub use module_inventory::*; pub use module_jump_hop::*; pub use module_npc::*; pub use module_progression::*; +pub use module_puzzle_clear::*; pub use module_quest::*; pub use module_runtime::*; pub use module_runtime_item::*; @@ -36,6 +37,7 @@ mod match3d; mod migration; mod public_work; mod puzzle; +mod puzzle_clear; mod runtime; mod square_hole; mod visual_novel; @@ -54,6 +56,7 @@ pub use jump_hop::*; pub use match3d::*; pub use migration::*; pub use public_work::*; +pub use puzzle_clear::*; pub use runtime::*; pub use square_hole::*; pub use visual_novel::*; diff --git a/server-rs/crates/spacetime-module/src/migration.rs b/server-rs/crates/spacetime-module/src/migration.rs index dfd882fb..3ffb61c3 100644 --- a/server-rs/crates/spacetime-module/src/migration.rs +++ b/server-rs/crates/spacetime-module/src/migration.rs @@ -23,6 +23,10 @@ use crate::puzzle::{ puzzle_agent_message, puzzle_agent_session, puzzle_event, puzzle_leaderboard_entry, puzzle_runtime_run, puzzle_work_profile, }; +use crate::puzzle_clear::tables::{ + puzzle_clear_agent_session, puzzle_clear_event, puzzle_clear_runtime_run, + puzzle_clear_work_profile, +}; use crate::square_hole::tables::{ square_hole_agent_message, square_hole_agent_session, square_hole_runtime_run, square_hole_work_profile, @@ -230,6 +234,10 @@ macro_rules! migration_tables { puzzle_event, puzzle_runtime_run, puzzle_leaderboard_entry, + puzzle_clear_agent_session, + puzzle_clear_work_profile, + puzzle_clear_runtime_run, + puzzle_clear_event, bark_battle_draft_config, bark_battle_published_config, bark_battle_runtime_run, @@ -1321,6 +1329,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde if matches!( table_name, "jump_hop_work_profile" + | "puzzle_clear_work_profile" | "square_hole_work_profile" | "visual_novel_work_profile" | "bark_battle_published_config" @@ -1330,6 +1339,12 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde object .entry("visible".to_string()) .or_insert_with(|| serde_json::Value::Bool(true)); + if table_name == "puzzle_clear_work_profile" { + // 中文注释:拼消消底图提示词字段晚于作品表加入,旧迁移包按空提示词兼容。 + object + .entry("board_background_prompt".to_string()) + .or_insert(serde_json::Value::Null); + } if table_name == "jump_hop_work_profile" { // 中文注释:跳一跳主题返回按钮资产晚于首版作品表加入,旧迁移包按未生成按钮兼容。 object diff --git a/server-rs/crates/spacetime-module/src/public_work.rs b/server-rs/crates/spacetime-module/src/public_work.rs index e4a2edb5..21dea02e 100644 --- a/server-rs/crates/spacetime-module/src/public_work.rs +++ b/server-rs/crates/spacetime-module/src/public_work.rs @@ -1,4 +1,5 @@ use crate::puzzle::{PuzzleGalleryCardViewRow, puzzle_gallery_card_view, puzzle_gallery_view}; +use crate::puzzle_clear::{puzzle_clear_gallery_card_view, puzzle_clear_gallery_view}; use crate::*; use module_custom_world::{CustomWorldGalleryEntrySnapshot, CustomWorldProfileSnapshot}; use module_puzzle::PuzzleWorkProfile; @@ -17,6 +18,11 @@ pub fn public_work_gallery_entry(ctx: &AnonymousViewContext) -> Vec Vec PublicWorkDetailEntry { gallery_to_detail(entry, detail_payload_json) } +fn map_puzzle_clear_gallery_entry(row: PuzzleClearGalleryCardViewRow) -> PublicWorkGalleryEntry { + let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); + + PublicWorkGalleryEntry { + source_type: "puzzle-clear".to_string(), + work_id: row.work_id, + profile_id: row.profile_id, + source_session_id: None, + public_work_code: row.public_work_code, + owner_user_id: row.owner_user_id, + author_display_name: row.author_display_name, + world_name: row.work_title, + subtitle: "拼消消".to_string(), + summary_text: row.work_description, + cover_image_src: row.cover_image_src, + cover_asset_id: None, + theme_tags: fallback_tags(vec![row.theme_prompt], &["拼消消"]), + play_count: row.play_count, + remix_count: 0, + like_count: 0, + published_at_micros: row.published_at_micros, + updated_at_micros: row.updated_at_micros, + sort_time_micros, + } +} + +fn map_puzzle_clear_detail_entry(row: PuzzleClearGalleryViewRow) -> PublicWorkDetailEntry { + let entry = PublicWorkGalleryEntry { + source_type: "puzzle-clear".to_string(), + work_id: row.work_id, + profile_id: row.profile_id.clone(), + source_session_id: empty_string_to_option(row.source_session_id), + public_work_code: build_prefixed_public_work_code("PC", &row.profile_id), + owner_user_id: row.owner_user_id, + author_display_name: row.author_display_name, + world_name: row.work_title, + subtitle: "拼消消".to_string(), + summary_text: row.work_description, + cover_image_src: row.cover_image_src, + cover_asset_id: None, + theme_tags: fallback_tags(vec![row.theme_prompt.clone()], &["拼消消"]), + play_count: row.play_count, + remix_count: 0, + like_count: 0, + published_at_micros: row.published_at_micros, + updated_at_micros: row.updated_at_micros, + sort_time_micros: row.published_at_micros.unwrap_or(row.updated_at_micros), + }; + let detail_payload_json = json_string(json!({ + "sourceType": "puzzle-clear", + "themePrompt": row.theme_prompt, + "patternGroupCount": row.pattern_groups.len(), + "cardAssetCount": row.card_assets.len(), + "generationStatus": row.generation_status, + "hasBoardBackground": row.board_background_asset.is_some(), + })); + gallery_to_detail(entry, detail_payload_json) +} + fn map_custom_world_gallery_entry(row: CustomWorldGalleryEntrySnapshot) -> PublicWorkGalleryEntry { PublicWorkGalleryEntry { source_type: "custom-world".to_string(), diff --git a/server-rs/crates/spacetime-module/src/puzzle_clear.rs b/server-rs/crates/spacetime-module/src/puzzle_clear.rs new file mode 100644 index 00000000..ce917767 --- /dev/null +++ b/server-rs/crates/spacetime-module/src/puzzle_clear.rs @@ -0,0 +1,1647 @@ +pub(crate) mod tables; +mod types; + +pub use tables::*; +pub use types::*; + +use crate::{ProcedureContext, ReducerContext, SpacetimeType, Table, Timestamp, json}; +use module_puzzle_clear::{ + PUZZLE_CLEAR_LEVEL_DURATION_SECONDS, PuzzleClearBoard, PuzzleClearCard, PuzzleClearDeck, + PuzzleClearMove, PuzzleClearRunSnapshot as DomainRunSnapshot, advance_puzzle_clear_level, + apply_puzzle_clear_swap, create_puzzle_clear_board, fail_puzzle_clear_level_on_timeout, + parse_puzzle_clear_orientation, parse_puzzle_clear_shape_kind, puzzle_clear_level_configs, + retry_puzzle_clear_level, start_puzzle_clear_run, +}; +use serde::Serialize; +use serde::de::DeserializeOwned; +use spacetimedb::AnonymousViewContext; +use std::collections::BTreeMap; + +#[spacetimedb::view(accessor = puzzle_clear_gallery_view, public)] +pub fn puzzle_clear_gallery_view(ctx: &AnonymousViewContext) -> Vec { + let mut items = ctx + .db + .puzzle_clear_work_profile() + .by_puzzle_clear_work_publication_status() + .filter(PUZZLE_CLEAR_PUBLICATION_PUBLISHED) + .filter(|row| row.visible) + .filter_map(|row| match build_gallery_view_row(&row) { + Ok(item) => Some(item), + Err(error) => { + log::warn!( + "拼消消公开广场 view 跳过损坏作品 profile_id={}: {}", + row.profile_id, + error + ); + None + } + }) + .collect::>(); + items.sort_by(|left, right| { + right + .updated_at_micros + .cmp(&left.updated_at_micros) + .then_with(|| left.profile_id.cmp(&right.profile_id)) + }); + items +} + +#[spacetimedb::view(accessor = puzzle_clear_gallery_card_view, public)] +pub fn puzzle_clear_gallery_card_view( + ctx: &AnonymousViewContext, +) -> Vec { + puzzle_clear_gallery_view(ctx) + .into_iter() + .map(|row| PuzzleClearGalleryCardViewRow { + public_work_code: build_puzzle_clear_public_work_code(&row.profile_id), + work_id: row.work_id, + profile_id: row.profile_id, + owner_user_id: row.owner_user_id, + author_display_name: row.author_display_name, + work_title: row.work_title, + work_description: row.work_description, + theme_prompt: row.theme_prompt, + cover_image_src: row.cover_image_src, + publication_status: row.publication_status, + play_count: row.play_count, + updated_at_micros: row.updated_at_micros, + published_at_micros: row.published_at_micros, + generation_status: row.generation_status, + }) + .collect() +} + +#[derive(Clone, Debug, PartialEq, SpacetimeType)] +pub struct PuzzleClearGalleryViewRow { + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub source_session_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + pub board_background_prompt: String, + pub card_back_image_src: Option, + pub atlas_asset: PuzzleClearImageAssetSnapshot, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub cover_image_src: Option, + pub publication_status: String, + pub publish_ready: bool, + pub play_count: u32, + pub generation_status: String, + pub updated_at_micros: i64, + pub published_at_micros: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearGalleryCardViewRow { + pub public_work_code: String, + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub cover_image_src: Option, + pub publication_status: String, + pub play_count: u32, + pub updated_at_micros: i64, + pub published_at_micros: Option, + pub generation_status: String, +} + +#[spacetimedb::procedure] +pub fn create_puzzle_clear_agent_session( + ctx: &mut ProcedureContext, + input: PuzzleClearAgentSessionCreateInput, +) -> PuzzleClearAgentSessionProcedureResult { + match ctx.try_with_tx(|tx| create_puzzle_clear_agent_session_tx(tx, input.clone())) { + Ok(session) => session_result(session), + Err(message) => session_error(message), + } +} + +#[spacetimedb::procedure] +pub fn get_puzzle_clear_agent_session( + ctx: &mut ProcedureContext, + input: PuzzleClearAgentSessionGetInput, +) -> PuzzleClearAgentSessionProcedureResult { + match ctx.try_with_tx(|tx| get_puzzle_clear_agent_session_tx(tx, input.clone())) { + Ok(session) => session_result(session), + Err(message) => session_error(message), + } +} + +#[spacetimedb::procedure] +pub fn compile_puzzle_clear_draft( + ctx: &mut ProcedureContext, + input: PuzzleClearDraftCompileInput, +) -> PuzzleClearAgentSessionProcedureResult { + match ctx.try_with_tx(|tx| compile_puzzle_clear_draft_tx(tx, input.clone())) { + Ok(session) => session_result(session), + Err(message) => session_error(message), + } +} + +#[spacetimedb::procedure] +pub fn get_puzzle_clear_work_profile( + ctx: &mut ProcedureContext, + input: PuzzleClearWorkGetInput, +) -> PuzzleClearWorkProcedureResult { + match ctx.try_with_tx(|tx| get_puzzle_clear_work_profile_tx(tx, input.clone())) { + Ok(work) => work_result(work), + Err(message) => work_error(message), + } +} + +#[spacetimedb::procedure] +pub fn update_puzzle_clear_work( + ctx: &mut ProcedureContext, + input: PuzzleClearWorkUpdateInput, +) -> PuzzleClearWorkProcedureResult { + match ctx.try_with_tx(|tx| update_puzzle_clear_work_tx(tx, input.clone())) { + Ok(work) => work_result(work), + Err(message) => work_error(message), + } +} + +#[spacetimedb::procedure] +pub fn publish_puzzle_clear_work( + ctx: &mut ProcedureContext, + input: PuzzleClearWorkPublishInput, +) -> PuzzleClearWorkProcedureResult { + match ctx.try_with_tx(|tx| publish_puzzle_clear_work_tx(tx, input.clone())) { + Ok(work) => work_result(work), + Err(message) => work_error(message), + } +} + +#[spacetimedb::procedure] +pub fn list_puzzle_clear_works( + ctx: &mut ProcedureContext, + input: PuzzleClearWorksListInput, +) -> PuzzleClearWorksProcedureResult { + match ctx.try_with_tx(|tx| list_puzzle_clear_works_tx(tx, input.clone())) { + Ok(items) => PuzzleClearWorksProcedureResult { + ok: true, + items, + error_message: None, + }, + Err(message) => PuzzleClearWorksProcedureResult { + ok: false, + items: Vec::new(), + error_message: Some(message), + }, + } +} + +#[spacetimedb::procedure] +pub fn start_puzzle_clear_runtime_run( + ctx: &mut ProcedureContext, + input: PuzzleClearRunStartInput, +) -> PuzzleClearRunProcedureResult { + match ctx.try_with_tx(|tx| start_puzzle_clear_runtime_run_tx(tx, input.clone())) { + Ok(run) => run_result(run), + Err(message) => run_error(message), + } +} + +#[spacetimedb::procedure] +pub fn get_puzzle_clear_runtime_run( + ctx: &mut ProcedureContext, + input: PuzzleClearRunGetInput, +) -> PuzzleClearRunProcedureResult { + match ctx.try_with_tx(|tx| get_puzzle_clear_runtime_run_tx(tx, input.clone())) { + Ok(run) => run_result(run), + Err(message) => run_error(message), + } +} + +#[spacetimedb::procedure] +pub fn swap_puzzle_clear_cards( + ctx: &mut ProcedureContext, + input: PuzzleClearRunSwapInput, +) -> PuzzleClearRunProcedureResult { + match ctx.try_with_tx(|tx| swap_puzzle_clear_cards_tx(tx, input.clone())) { + Ok(run) => run_result(run), + Err(message) => run_error(message), + } +} + +#[spacetimedb::procedure] +pub fn retry_puzzle_clear_level_run( + ctx: &mut ProcedureContext, + input: PuzzleClearRunRetryLevelInput, +) -> PuzzleClearRunProcedureResult { + match ctx.try_with_tx(|tx| retry_puzzle_clear_level_run_tx(tx, input.clone())) { + Ok(run) => run_result(run), + Err(message) => run_error(message), + } +} + +#[spacetimedb::procedure] +pub fn advance_puzzle_clear_next_level( + ctx: &mut ProcedureContext, + input: PuzzleClearRunNextLevelInput, +) -> PuzzleClearRunProcedureResult { + match ctx.try_with_tx(|tx| advance_puzzle_clear_next_level_tx(tx, input.clone())) { + Ok(run) => run_result(run), + Err(message) => run_error(message), + } +} + +#[spacetimedb::procedure] +pub fn mark_puzzle_clear_level_time_up( + ctx: &mut ProcedureContext, + input: PuzzleClearRunTimeUpInput, +) -> PuzzleClearRunProcedureResult { + match ctx.try_with_tx(|tx| mark_puzzle_clear_level_time_up_tx(tx, input.clone())) { + Ok(run) => run_result(run), + Err(message) => run_error(message), + } +} + +fn create_puzzle_clear_agent_session_tx( + ctx: &ReducerContext, + input: PuzzleClearAgentSessionCreateInput, +) -> Result { + require_non_empty(&input.session_id, "puzzle_clear session_id")?; + require_non_empty(&input.owner_user_id, "puzzle_clear owner_user_id")?; + require_non_empty(&input.work_title, "work_title")?; + require_non_empty(&input.theme_prompt, "theme_prompt")?; + if ctx + .db + .puzzle_clear_agent_session() + .session_id() + .find(&input.session_id) + .is_some() + { + return Err("puzzle_clear_agent_session.session_id 已存在".to_string()); + } + + let created_at = Timestamp::from_micros_since_unix_epoch(input.created_at_micros); + let draft = PuzzleClearDraftSnapshot { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: None, + work_title: clean_string(&input.work_title, PUZZLE_CLEAR_TEMPLATE_NAME), + work_description: input.work_description.trim().to_string(), + theme_prompt: clean_string(&input.theme_prompt, PUZZLE_CLEAR_TEMPLATE_NAME), + generate_board_background: input.generate_board_background, + board_background_asset: input + .board_background_asset_json + .as_deref() + .map(parse_json) + .transpose()?, + board_background_prompt: clean_string(&input.board_background_prompt, &input.theme_prompt), + card_back_image_src: Some(PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC.to_string()), + atlas_asset: None, + pattern_groups: Vec::new(), + card_assets: Vec::new(), + generation_status: PUZZLE_CLEAR_GENERATION_DRAFT.to_string(), + }; + let owner_user_id = input.owner_user_id.clone(); + let session_id = input.session_id.clone(); + ctx.db + .puzzle_clear_agent_session() + .insert(PuzzleClearAgentSessionRow { + session_id: input.session_id.clone(), + owner_user_id: input.owner_user_id, + status: PUZZLE_CLEAR_GENERATION_DRAFT.to_string(), + draft_json: to_json_string(&draft), + published_profile_id: String::new(), + created_at, + updated_at: created_at, + }); + + get_puzzle_clear_agent_session_tx( + ctx, + PuzzleClearAgentSessionGetInput { + session_id, + owner_user_id, + }, + ) +} + +fn get_puzzle_clear_agent_session_tx( + ctx: &ReducerContext, + input: PuzzleClearAgentSessionGetInput, +) -> Result { + let row = find_owned_session(ctx, &input.session_id, &input.owner_user_id)?; + build_session_snapshot(&row) +} + +fn compile_puzzle_clear_draft_tx( + ctx: &ReducerContext, + input: PuzzleClearDraftCompileInput, +) -> Result { + require_non_empty(&input.profile_id, "puzzle_clear profile_id")?; + let session = find_owned_session(ctx, &input.session_id, &input.owner_user_id)?; + if input.generation_status.as_deref() == Some(PUZZLE_CLEAR_GENERATION_FAILED) { + return mark_puzzle_clear_generation_failed_tx(ctx, input, session); + } + let pattern_groups: Vec = input + .pattern_groups_json + .as_deref() + .map(parse_json) + .transpose()? + .ok_or_else(|| "puzzle_clear pattern_groups 缺少真实生成资产".to_string())?; + let atlas_asset: PuzzleClearImageAssetSnapshot = input + .atlas_asset_json + .as_deref() + .map(parse_json) + .transpose()? + .ok_or_else(|| "puzzle_clear atlas_asset 缺少真实生成资产".to_string())?; + let card_assets: Vec = input + .card_assets_json + .as_deref() + .map(parse_json) + .transpose()? + .ok_or_else(|| "puzzle_clear card_assets 缺少真实生成资产".to_string())?; + if card_assets.is_empty() { + return Err("puzzle_clear card_assets 不能为空".to_string()); + } + if !is_real_puzzle_clear_asset( + atlas_asset.asset_object_id.as_str(), + atlas_asset.image_object_key.as_str(), + atlas_asset.image_src.as_str(), + ) { + return Err("puzzle_clear atlas_asset 缺少真实生成资产".to_string()); + } + if card_assets.iter().any(|asset| { + !is_real_puzzle_clear_asset( + asset.asset_object_id.as_str(), + asset.image_object_key.as_str(), + asset.image_src.as_str(), + ) + }) { + return Err("puzzle_clear card_assets 缺少真实生成资产".to_string()); + } + let board_background_asset = input + .board_background_asset_json + .as_deref() + .map(parse_json) + .transpose()?; + let generation_status = input + .generation_status + .clone() + .unwrap_or_else(|| PUZZLE_CLEAR_GENERATION_READY.to_string()); + let compiled_at = Timestamp::from_micros_since_unix_epoch(input.compiled_at_micros); + let draft = PuzzleClearDraftSnapshot { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: Some(input.profile_id.clone()), + work_title: clean_string(&input.work_title, PUZZLE_CLEAR_TEMPLATE_NAME), + work_description: input.work_description.trim().to_string(), + theme_prompt: clean_string(&input.theme_prompt, PUZZLE_CLEAR_TEMPLATE_NAME), + generate_board_background: input.generate_board_background, + board_background_asset: board_background_asset.clone(), + board_background_prompt: clean_string(&input.board_background_prompt, &input.theme_prompt), + card_back_image_src: Some(PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC.to_string()), + atlas_asset: Some(atlas_asset.clone()), + pattern_groups: pattern_groups.clone(), + card_assets: card_assets.clone(), + generation_status: generation_status.clone(), + }; + let row = PuzzleClearWorkProfileRow { + profile_id: input.profile_id.clone(), + work_id: input.profile_id.clone(), + owner_user_id: input.owner_user_id.clone(), + source_session_id: input.session_id.clone(), + author_display_name: clean_string(&input.author_display_name, "拼消消玩家"), + work_title: draft.work_title.clone(), + work_description: draft.work_description.clone(), + theme_prompt: draft.theme_prompt.clone(), + generate_board_background: draft.generate_board_background, + board_background_asset_json: board_background_asset + .as_ref() + .map(to_json_string) + .unwrap_or_default(), + board_background_prompt: clean_optional(&draft.board_background_prompt), + card_back_image_src: PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC.to_string(), + atlas_asset_json: to_json_string(&atlas_asset), + pattern_groups_json: to_json_string(&pattern_groups), + card_assets_json: to_json_string(&card_assets), + cover_image_src: cover_image_src(&board_background_asset, &atlas_asset), + generation_status, + publication_status: PUZZLE_CLEAR_PUBLICATION_DRAFT.to_string(), + play_count: 0, + updated_at: compiled_at, + published_at: None, + visible: true, + }; + upsert_work(ctx, row); + replace_session( + ctx, + &session, + PuzzleClearAgentSessionRow { + status: draft.generation_status.clone(), + draft_json: to_json_string(&draft), + published_profile_id: input.profile_id, + updated_at: compiled_at, + ..clone_session(&session) + }, + ); + + get_puzzle_clear_agent_session_tx( + ctx, + PuzzleClearAgentSessionGetInput { + session_id: input.session_id, + owner_user_id: input.owner_user_id, + }, + ) +} + +fn mark_puzzle_clear_generation_failed_tx( + ctx: &ReducerContext, + input: PuzzleClearDraftCompileInput, + session: PuzzleClearAgentSessionRow, +) -> Result { + let failed_at = Timestamp::from_micros_since_unix_epoch(input.compiled_at_micros); + let mut draft = if session.draft_json.trim().is_empty() { + None + } else { + parse_json::(&session.draft_json).ok() + } + .unwrap_or_else(|| PuzzleClearDraftSnapshot { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: Some(input.profile_id.clone()), + work_title: clean_string(&input.work_title, PUZZLE_CLEAR_TEMPLATE_NAME), + work_description: input.work_description.trim().to_string(), + theme_prompt: clean_string(&input.theme_prompt, PUZZLE_CLEAR_TEMPLATE_NAME), + generate_board_background: input.generate_board_background, + board_background_asset: input + .board_background_asset_json + .as_deref() + .and_then(|json| parse_json::(json).ok()), + board_background_prompt: clean_string(&input.board_background_prompt, &input.theme_prompt), + card_back_image_src: Some(PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC.to_string()), + atlas_asset: None, + pattern_groups: Vec::new(), + card_assets: Vec::new(), + generation_status: PUZZLE_CLEAR_GENERATION_FAILED.to_string(), + }); + draft.profile_id = Some(input.profile_id.clone()); + draft.work_title = clean_string(&input.work_title, PUZZLE_CLEAR_TEMPLATE_NAME); + draft.work_description = input.work_description.trim().to_string(); + draft.theme_prompt = clean_string(&input.theme_prompt, PUZZLE_CLEAR_TEMPLATE_NAME); + draft.generate_board_background = input.generate_board_background; + draft.board_background_prompt = + clean_string(&input.board_background_prompt, &input.theme_prompt); + let existing_board_background_asset = draft.board_background_asset.take(); + draft.board_background_asset = input + .board_background_asset_json + .as_deref() + .map(parse_json) + .transpose()? + .or(existing_board_background_asset); + draft.generation_status = PUZZLE_CLEAR_GENERATION_FAILED.to_string(); + + replace_session( + ctx, + &session, + PuzzleClearAgentSessionRow { + status: PUZZLE_CLEAR_GENERATION_FAILED.to_string(), + draft_json: to_json_string(&draft), + updated_at: failed_at, + ..clone_session(&session) + }, + ); + + get_puzzle_clear_agent_session_tx( + ctx, + PuzzleClearAgentSessionGetInput { + session_id: input.session_id, + owner_user_id: input.owner_user_id, + }, + ) +} + +fn get_puzzle_clear_work_profile_tx( + ctx: &ReducerContext, + input: PuzzleClearWorkGetInput, +) -> Result { + let row = find_work(ctx, &input.profile_id)?; + if !input.owner_user_id.trim().is_empty() && row.owner_user_id != input.owner_user_id { + return Err("无权访问该 puzzle_clear work".to_string()); + } + build_work_snapshot(&row) +} + +fn update_puzzle_clear_work_tx( + ctx: &ReducerContext, + input: PuzzleClearWorkUpdateInput, +) -> Result { + let row = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?; + let updated_at = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros); + let board_background_asset = input + .board_background_asset_json + .as_deref() + .map(parse_json::) + .transpose()?; + let atlas_asset = parse_json::(&row.atlas_asset_json)?; + let mut next = clone_work(&row); + next.work_title = clean_string(&input.work_title, &row.work_title); + next.work_description = input.work_description.trim().to_string(); + next.theme_prompt = clean_string(&input.theme_prompt, &row.theme_prompt); + next.generate_board_background = input.generate_board_background; + let next_board_background_prompt = + clean_string(&input.board_background_prompt, &input.theme_prompt); + next.board_background_prompt = clean_optional(&next_board_background_prompt); + next.board_background_asset_json = board_background_asset + .as_ref() + .map(to_json_string) + .unwrap_or_default(); + next.cover_image_src = cover_image_src(&board_background_asset, &atlas_asset); + next.updated_at = updated_at; + replace_work(ctx, &row, next); + let updated = find_work(ctx, &row.profile_id)?; + sync_session_from_work(ctx, &updated, updated_at)?; + build_work_snapshot(&updated) +} + +fn publish_puzzle_clear_work_tx( + ctx: &ReducerContext, + input: PuzzleClearWorkPublishInput, +) -> Result { + let row = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?; + let snapshot = build_work_snapshot(&row)?; + if !snapshot.publish_ready { + return Err("拼消消发布需要 atlas、切片卡牌和标题齐备".to_string()); + } + let published_at = Timestamp::from_micros_since_unix_epoch(input.published_at_micros); + replace_work( + ctx, + &row, + PuzzleClearWorkProfileRow { + publication_status: PUZZLE_CLEAR_PUBLICATION_PUBLISHED.to_string(), + updated_at: published_at, + published_at: Some(published_at), + ..clone_work(&row) + }, + ); + if let Some(session) = ctx + .db + .puzzle_clear_agent_session() + .session_id() + .find(&row.source_session_id) + { + replace_session( + ctx, + &session, + PuzzleClearAgentSessionRow { + status: PUZZLE_CLEAR_GENERATION_READY.to_string(), + updated_at: published_at, + ..clone_session(&session) + }, + ); + } + let updated = find_work(ctx, &row.profile_id)?; + build_work_snapshot(&updated) +} + +fn list_puzzle_clear_works_tx( + ctx: &ReducerContext, + input: PuzzleClearWorksListInput, +) -> Result, String> { + let mut rows = if input.owner_user_id.trim().is_empty() { + ctx.db + .puzzle_clear_work_profile() + .iter() + .collect::>() + } else { + ctx.db + .puzzle_clear_work_profile() + .by_puzzle_clear_work_owner_user_id() + .filter(input.owner_user_id.as_str()) + .collect::>() + }; + if input.published_only { + rows.retain(|row| row.publication_status == PUZZLE_CLEAR_PUBLICATION_PUBLISHED); + } + rows.sort_by(|left, right| { + right + .updated_at + .cmp(&left.updated_at) + .then_with(|| left.profile_id.cmp(&right.profile_id)) + }); + rows.into_iter() + .map(|row| build_work_snapshot(&row)) + .collect() +} + +fn start_puzzle_clear_runtime_run_tx( + ctx: &ReducerContext, + input: PuzzleClearRunStartInput, +) -> Result { + require_non_empty(&input.run_id, "puzzle_clear run_id")?; + let work = find_work(ctx, &input.profile_id)?; + if work.publication_status != PUZZLE_CLEAR_PUBLICATION_PUBLISHED { + return Err("拼消消 runtime 只能启动已发布作品".to_string()); + } + let cards = domain_cards_from_work(&work)?; + let (board, deck) = build_level_board_and_deck(1, &work.profile_id, &cards)?; + let domain_run = start_puzzle_clear_run( + input.run_id.clone(), + input.owner_user_id.clone(), + input.profile_id.clone(), + board, + deck, + input.started_at_ms.max(0) as u64, + ) + .map_err(|error| error.to_string())?; + upsert_run(ctx, &domain_run, input.started_at_ms); + increment_work_play_count(ctx, &work, input.started_at_ms); + insert_event( + ctx, + input.client_event_id, + input.owner_user_id.clone(), + input.profile_id, + input.run_id, + PUZZLE_CLEAR_EVENT_RUN_STARTED, + None, + input.started_at_ms, + ); + build_runtime_snapshot(&domain_run) +} + +fn get_puzzle_clear_runtime_run_tx( + ctx: &ReducerContext, + input: PuzzleClearRunGetInput, +) -> Result { + let row = find_owned_run(ctx, &input.run_id, &input.owner_user_id)?; + let snapshot = parse_json::(&row.snapshot_json)?; + build_runtime_snapshot(&snapshot) +} + +fn swap_puzzle_clear_cards_tx( + ctx: &ReducerContext, + input: PuzzleClearRunSwapInput, +) -> Result { + let row = find_owned_run(ctx, &input.run_id, &input.owner_user_id)?; + let snapshot = parse_json::(&row.snapshot_json)?; + let next = apply_puzzle_clear_swap( + &snapshot, + PuzzleClearMove { + from_row: input.from_row, + from_col: input.from_col, + to_row: input.to_row, + to_col: input.to_col, + }, + input.swapped_at_ms.max(0) as u64, + ) + .map_err(|error| error.to_string())?; + replace_run(ctx, &row, &next, input.swapped_at_ms); + insert_event( + ctx, + input.client_action_id, + input.owner_user_id.clone(), + next.profile_id.clone(), + input.run_id, + PUZZLE_CLEAR_EVENT_SWAP, + Some(runtime_event_result(&snapshot, &next, input.swapped_at_ms)), + input.swapped_at_ms, + ); + insert_terminal_runtime_event_if_needed( + ctx, + &snapshot, + &next, + input.owner_user_id, + input.swapped_at_ms, + ); + build_runtime_snapshot(&next) +} + +fn retry_puzzle_clear_level_run_tx( + ctx: &ReducerContext, + input: PuzzleClearRunRetryLevelInput, +) -> Result { + let row = find_owned_run(ctx, &input.run_id, &input.owner_user_id)?; + let snapshot = parse_json::(&row.snapshot_json)?; + let work = find_work(ctx, &snapshot.profile_id)?; + let cards = domain_cards_from_work(&work)?; + let (board, deck) = build_level_board_and_deck( + snapshot.level_index, + &format!("{}-retry-{}", snapshot.profile_id, input.restarted_at_ms), + &cards, + )?; + let next = + retry_puzzle_clear_level(&snapshot, board, deck, input.restarted_at_ms.max(0) as u64) + .map_err(|error| error.to_string())?; + replace_run(ctx, &row, &next, input.restarted_at_ms); + insert_event( + ctx, + input.client_action_id, + input.owner_user_id.clone(), + next.profile_id.clone(), + input.run_id, + PUZZLE_CLEAR_EVENT_RETRY_LEVEL, + None, + input.restarted_at_ms, + ); + build_runtime_snapshot(&next) +} + +fn advance_puzzle_clear_next_level_tx( + ctx: &ReducerContext, + input: PuzzleClearRunNextLevelInput, +) -> Result { + let row = find_owned_run(ctx, &input.run_id, &input.owner_user_id)?; + let snapshot = parse_json::(&row.snapshot_json)?; + let next_level = snapshot.level_index.saturating_add(1); + let work = find_work(ctx, &snapshot.profile_id)?; + let cards = domain_cards_from_work(&work)?; + let (board, deck) = build_level_board_and_deck( + next_level, + &format!("{}-level-{next_level}", snapshot.profile_id), + &cards, + )?; + let next = + advance_puzzle_clear_level(&snapshot, board, deck, input.started_at_ms.max(0) as u64) + .map_err(|error| error.to_string())?; + replace_run(ctx, &row, &next, input.started_at_ms); + insert_event( + ctx, + input.client_action_id, + input.owner_user_id.clone(), + next.profile_id.clone(), + input.run_id, + PUZZLE_CLEAR_EVENT_NEXT_LEVEL, + Some(next.level_index.to_string()), + input.started_at_ms, + ); + build_runtime_snapshot(&next) +} + +fn mark_puzzle_clear_level_time_up_tx( + ctx: &ReducerContext, + input: PuzzleClearRunTimeUpInput, +) -> Result { + let row = find_owned_run(ctx, &input.run_id, &input.owner_user_id)?; + let snapshot = parse_json::(&row.snapshot_json)?; + let next = fail_puzzle_clear_level_on_timeout(&snapshot, input.occurred_at_ms.max(0) as u64) + .map_err(|error| error.to_string())?; + replace_run(ctx, &row, &next, input.occurred_at_ms); + insert_event( + ctx, + input.client_action_id, + input.owner_user_id.clone(), + next.profile_id.clone(), + input.run_id, + PUZZLE_CLEAR_EVENT_TIME_UP, + Some(runtime_event_result(&snapshot, &next, input.occurred_at_ms)), + input.occurred_at_ms, + ); + insert_terminal_runtime_event_if_needed( + ctx, + &snapshot, + &next, + input.owner_user_id, + input.occurred_at_ms, + ); + build_runtime_snapshot(&next) +} + +fn build_gallery_view_row( + row: &PuzzleClearWorkProfileRow, +) -> Result { + let work = build_work_snapshot(row)?; + Ok(PuzzleClearGalleryViewRow { + work_id: work.work_id, + profile_id: work.profile_id, + owner_user_id: work.owner_user_id, + source_session_id: work.source_session_id, + author_display_name: work.author_display_name, + work_title: work.work_title, + work_description: work.work_description, + theme_prompt: work.theme_prompt, + generate_board_background: work.generate_board_background, + board_background_asset: work.board_background_asset, + board_background_prompt: work.board_background_prompt, + card_back_image_src: work.card_back_image_src, + atlas_asset: work.atlas_asset, + pattern_groups: work.pattern_groups, + card_assets: work.card_assets, + cover_image_src: work.cover_image_src, + publication_status: work.publication_status, + publish_ready: work.publish_ready, + play_count: work.play_count, + generation_status: work.generation_status, + updated_at_micros: work.updated_at_micros, + published_at_micros: work.published_at_micros, + }) +} + +pub fn build_puzzle_clear_public_work_code(profile_id: &str) -> String { + let normalized = profile_id + .chars() + .filter(|character| character.is_ascii_alphanumeric()) + .flat_map(|character| character.to_uppercase()) + .collect::(); + let suffix_source = if normalized.is_empty() { + "00000000".to_string() + } else { + normalized + }; + let suffix = if suffix_source.len() > 8 { + suffix_source[suffix_source.len() - 8..].to_string() + } else { + format!("{suffix_source:0>8}") + }; + format!("PC-{suffix}") +} + +fn build_session_snapshot( + row: &PuzzleClearAgentSessionRow, +) -> Result { + Ok(PuzzleClearAgentSessionSnapshot { + session_id: row.session_id.clone(), + owner_user_id: row.owner_user_id.clone(), + status: row.status.clone(), + draft: clean_optional(&row.draft_json) + .map(|value| parse_json(&value)) + .transpose()?, + published_profile_id: clean_optional(&row.published_profile_id), + created_at_micros: row.created_at.to_micros_since_unix_epoch(), + updated_at_micros: row.updated_at.to_micros_since_unix_epoch(), + }) +} + +fn build_work_snapshot(row: &PuzzleClearWorkProfileRow) -> Result { + let atlas_asset = parse_json(&row.atlas_asset_json)?; + let card_assets = parse_json_or_default(&row.card_assets_json); + let pattern_groups = parse_json_or_default(&row.pattern_groups_json); + Ok(PuzzleClearWorkSnapshot { + work_id: row.work_id.clone(), + profile_id: row.profile_id.clone(), + owner_user_id: row.owner_user_id.clone(), + source_session_id: row.source_session_id.clone(), + author_display_name: row.author_display_name.clone(), + work_title: row.work_title.clone(), + work_description: row.work_description.clone(), + theme_prompt: row.theme_prompt.clone(), + generate_board_background: row.generate_board_background, + board_background_asset: clean_optional(&row.board_background_asset_json) + .map(|value| parse_json(&value)) + .transpose()?, + board_background_prompt: row + .board_background_prompt + .as_deref() + .and_then(clean_optional) + .unwrap_or_default(), + card_back_image_src: clean_optional(&row.card_back_image_src), + atlas_asset, + pattern_groups, + card_assets, + cover_image_src: clean_optional(&row.cover_image_src), + publication_status: row.publication_status.clone(), + publish_ready: is_publish_ready(row), + play_count: row.play_count, + generation_status: row.generation_status.clone(), + updated_at_micros: row.updated_at.to_micros_since_unix_epoch(), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), + }) +} + +fn sync_session_from_work( + ctx: &ReducerContext, + work: &PuzzleClearWorkProfileRow, + updated_at: Timestamp, +) -> Result<(), String> { + let Some(session) = ctx + .db + .puzzle_clear_agent_session() + .session_id() + .find(&work.source_session_id) + else { + return Ok(()); + }; + let draft = PuzzleClearDraftSnapshot { + template_id: PUZZLE_CLEAR_TEMPLATE_ID.to_string(), + template_name: PUZZLE_CLEAR_TEMPLATE_NAME.to_string(), + profile_id: Some(work.profile_id.clone()), + work_title: work.work_title.clone(), + work_description: work.work_description.clone(), + theme_prompt: work.theme_prompt.clone(), + generate_board_background: work.generate_board_background, + board_background_asset: clean_optional(&work.board_background_asset_json) + .map(|value| parse_json(&value)) + .transpose()?, + board_background_prompt: work + .board_background_prompt + .as_deref() + .and_then(clean_optional) + .unwrap_or_default(), + card_back_image_src: clean_optional(&work.card_back_image_src), + atlas_asset: Some(parse_json(&work.atlas_asset_json)?), + pattern_groups: parse_json_or_default(&work.pattern_groups_json), + card_assets: parse_json_or_default(&work.card_assets_json), + generation_status: work.generation_status.clone(), + }; + replace_session( + ctx, + &session, + PuzzleClearAgentSessionRow { + status: work.generation_status.clone(), + draft_json: to_json_string(&draft), + updated_at, + ..clone_session(&session) + }, + ); + Ok(()) +} + +fn build_level_board_and_deck( + level_index: u32, + seed: &str, + all_cards: &[PuzzleClearCard], +) -> Result<(PuzzleClearBoard, PuzzleClearDeck), String> { + let level = puzzle_clear_level_configs() + .into_iter() + .find(|config| config.level_index == level_index) + .ok_or_else(|| "拼消消关卡不存在".to_string())?; + let allowed = ordered_level_cards( + all_cards + .iter() + .filter(|card| level.unlocked_shapes.contains(&card.shape)) + .cloned() + .collect::>(), + seed, + level.target_clears as usize, + ); + let board = create_puzzle_clear_board(&level, seed, allowed.clone()) + .map_err(|error| error.to_string())?; + let board_total = (level.board_size * level.board_size) as usize; + let mut ready_columns = vec![Vec::new(); level.board_size as usize]; + for (index, card) in allowed.into_iter().skip(board_total).enumerate() { + ready_columns[index % level.board_size as usize].push(card); + } + Ok((board, PuzzleClearDeck { ready_columns })) +} + +fn ordered_level_cards( + cards: Vec, + seed: &str, + target_groups: usize, +) -> Vec { + let mut groups: BTreeMap> = BTreeMap::new(); + for card in cards { + groups.entry(card.group_id.clone()).or_default().push(card); + } + let mut grouped = groups.into_values().collect::>(); + grouped.sort_by(|left, right| { + let left_key = left + .first() + .map(|card| stable_level_group_key(seed, &card.group_id)) + .unwrap_or_default(); + let right_key = right + .first() + .map(|card| stable_level_group_key(seed, &card.group_id)) + .unwrap_or_default(); + left_key.cmp(&right_key) + }); + grouped + .into_iter() + .take(target_groups) + .flat_map(|mut group| { + group.sort_by_key(|card| (card.part_y, card.part_x)); + group + }) + .collect() +} + +fn stable_level_group_key(seed: &str, group_id: &str) -> u64 { + let mut state = 0xcbf2_9ce4_8422_2325u64; + for byte in seed.bytes().chain(group_id.bytes()) { + state ^= u64::from(byte); + state = state.wrapping_mul(0x1000_0000_01b3); + } + state +} + +fn domain_cards_from_work(row: &PuzzleClearWorkProfileRow) -> Result, String> { + let cards = parse_json::>(&row.card_assets_json)?; + Ok(cards + .into_iter() + .map(|card| PuzzleClearCard { + card_id: card.card_id, + group_id: card.group_id, + shape: parse_puzzle_clear_shape_kind(&card.shape), + orientation: parse_puzzle_clear_orientation(&card.orientation), + part_x: card.part_x, + part_y: card.part_y, + image_src: card.image_src, + image_object_key: card.image_object_key, + asset_object_id: card.asset_object_id, + source_atlas_cell: card.source_atlas_cell, + }) + .collect()) +} + +fn build_runtime_snapshot( + snapshot: &DomainRunSnapshot, +) -> Result { + let level = puzzle_clear_level_configs() + .into_iter() + .find(|config| config.level_index == snapshot.level_index) + .ok_or_else(|| "拼消消 runtime 关卡不存在".to_string())?; + Ok(PuzzleClearRuntimeSnapshot { + run_id: snapshot.run_id.clone(), + profile_id: snapshot.profile_id.clone(), + owner_user_id: snapshot.owner_user_id.clone(), + status: snapshot.status.as_str().to_string(), + level_index: snapshot.level_index, + clears_done: snapshot.clears_done, + target_clears: level.target_clears, + level_duration_seconds: PUZZLE_CLEAR_LEVEL_DURATION_SECONDS, + level_started_at_ms: snapshot.level_started_at_ms, + board: PuzzleClearBoardSnapshot { + rows: snapshot.board.rows, + cols: snapshot.board.cols, + cells: snapshot + .board + .cells + .iter() + .map(|cell| PuzzleClearBoardCellSnapshot { + row: cell.row, + col: cell.col, + card: cell.card.as_ref().map(card_asset_from_domain), + locked_group_id: cell.locked_group_id.clone(), + }) + .collect(), + }, + ready_columns: snapshot + .deck + .ready_columns + .iter() + .map(|column| column.iter().map(card_asset_from_domain).collect()) + .collect(), + started_at_ms: snapshot.started_at_ms, + finished_at_ms: snapshot.finished_at_ms, + }) +} + +fn card_asset_from_domain(card: &PuzzleClearCard) -> PuzzleClearCardAssetSnapshot { + PuzzleClearCardAssetSnapshot { + card_id: card.card_id.clone(), + group_id: card.group_id.clone(), + shape: card.shape.as_str().to_string(), + orientation: card.orientation.as_str().to_string(), + part_x: card.part_x, + part_y: card.part_y, + image_src: card.image_src.clone(), + image_object_key: card.image_object_key.clone(), + asset_object_id: card.asset_object_id.clone(), + source_atlas_cell: card.source_atlas_cell.clone(), + } +} + +#[cfg(test)] +fn default_pattern_groups() -> Vec { + module_puzzle_clear::plan_puzzle_clear_pattern_groups(128) + .unwrap_or_default() + .into_iter() + .map(|group| PuzzleClearPatternGroupSnapshot { + group_id: group.group_id, + shape: group.shape.as_str().to_string(), + width: group.width, + height: group.height, + atlas_x: group.atlas_x, + atlas_y: group.atlas_y, + atlas_width: group.atlas_width, + atlas_height: group.atlas_height, + }) + .collect() +} + +#[cfg(test)] +fn default_atlas_asset(profile_id: &str, prompt: &str) -> PuzzleClearImageAssetSnapshot { + PuzzleClearImageAssetSnapshot { + asset_id: format!("{profile_id}-atlas"), + image_src: format!("/generated-puzzle-clear-assets/{profile_id}/atlas.png"), + image_object_key: format!("generated-puzzle-clear-assets/{profile_id}/atlas.png"), + asset_object_id: format!("{profile_id}-atlas-object"), + generation_provider: "deterministic-placeholder".to_string(), + prompt: prompt.to_string(), + width: 3072, + height: 3072, + } +} + +#[cfg(test)] +fn default_card_assets( + profile_id: &str, + groups: &[PuzzleClearPatternGroupSnapshot], +) -> Vec { + let domain_groups = groups + .iter() + .map(|group| module_puzzle_clear::PuzzleClearPatternGroup { + group_id: group.group_id.clone(), + shape: parse_puzzle_clear_shape_kind(&group.shape), + width: group.width, + height: group.height, + atlas_x: group.atlas_x, + atlas_y: group.atlas_y, + atlas_width: group.atlas_width, + atlas_height: group.atlas_height, + }) + .collect::>(); + module_puzzle_clear::build_cards_from_groups( + &domain_groups, + &format!("/generated-puzzle-clear-assets/{profile_id}/cards"), + ) + .into_iter() + .map(|card| card_asset_from_domain(&card)) + .collect() +} + +fn cover_image_src( + board_background_asset: &Option, + atlas_asset: &PuzzleClearImageAssetSnapshot, +) -> String { + board_background_asset + .as_ref() + .map(|asset| asset.image_src.clone()) + .filter(|value| !value.trim().is_empty()) + .unwrap_or_else(|| atlas_asset.image_src.clone()) +} + +fn find_owned_session( + ctx: &ReducerContext, + session_id: &str, + owner_user_id: &str, +) -> Result { + let row = ctx + .db + .puzzle_clear_agent_session() + .session_id() + .find(&session_id.to_string()) + .ok_or_else(|| "puzzle_clear_agent_session 不存在".to_string())?; + if row.owner_user_id != owner_user_id { + return Err("无权访问该 puzzle_clear session".to_string()); + } + Ok(row) +} + +fn find_work(ctx: &ReducerContext, profile_id: &str) -> Result { + ctx.db + .puzzle_clear_work_profile() + .profile_id() + .find(&profile_id.to_string()) + .ok_or_else(|| "puzzle_clear_work_profile 不存在".to_string()) +} + +fn find_owned_work( + ctx: &ReducerContext, + profile_id: &str, + owner_user_id: &str, +) -> Result { + let row = find_work(ctx, profile_id)?; + if row.owner_user_id != owner_user_id { + return Err("无权访问该 puzzle_clear work".to_string()); + } + Ok(row) +} + +fn find_owned_run( + ctx: &ReducerContext, + run_id: &str, + owner_user_id: &str, +) -> Result { + let row = ctx + .db + .puzzle_clear_runtime_run() + .run_id() + .find(&run_id.to_string()) + .ok_or_else(|| "puzzle_clear_runtime_run 不存在".to_string())?; + if row.owner_user_id != owner_user_id { + return Err("无权访问该 puzzle_clear run".to_string()); + } + Ok(row) +} + +fn upsert_work(ctx: &ReducerContext, row: PuzzleClearWorkProfileRow) { + if let Some(old) = ctx + .db + .puzzle_clear_work_profile() + .profile_id() + .find(&row.profile_id) + { + ctx.db.puzzle_clear_work_profile().delete(old); + } + ctx.db.puzzle_clear_work_profile().insert(row); +} + +fn replace_work( + ctx: &ReducerContext, + old: &PuzzleClearWorkProfileRow, + next: PuzzleClearWorkProfileRow, +) { + ctx.db.puzzle_clear_work_profile().delete(clone_work(old)); + ctx.db.puzzle_clear_work_profile().insert(next); +} + +fn replace_session( + ctx: &ReducerContext, + old: &PuzzleClearAgentSessionRow, + next: PuzzleClearAgentSessionRow, +) { + ctx.db + .puzzle_clear_agent_session() + .delete(clone_session(old)); + ctx.db.puzzle_clear_agent_session().insert(next); +} + +fn upsert_run(ctx: &ReducerContext, snapshot: &DomainRunSnapshot, updated_at_ms: i64) { + if let Some(old) = ctx + .db + .puzzle_clear_runtime_run() + .run_id() + .find(&snapshot.run_id) + { + ctx.db.puzzle_clear_runtime_run().delete(old); + } + let created_at = Timestamp::from_micros_since_unix_epoch(updated_at_ms.saturating_mul(1000)); + ctx.db + .puzzle_clear_runtime_run() + .insert(run_row_from_snapshot(snapshot, created_at, created_at)); +} + +fn replace_run( + ctx: &ReducerContext, + old: &PuzzleClearRuntimeRunRow, + snapshot: &DomainRunSnapshot, + updated_at_ms: i64, +) { + ctx.db.puzzle_clear_runtime_run().delete(clone_run(old)); + ctx.db + .puzzle_clear_runtime_run() + .insert(run_row_from_snapshot( + snapshot, + old.created_at, + Timestamp::from_micros_since_unix_epoch(updated_at_ms.saturating_mul(1000)), + )); +} + +fn run_row_from_snapshot( + snapshot: &DomainRunSnapshot, + created_at: Timestamp, + updated_at: Timestamp, +) -> PuzzleClearRuntimeRunRow { + PuzzleClearRuntimeRunRow { + run_id: snapshot.run_id.clone(), + owner_user_id: snapshot.owner_user_id.clone(), + profile_id: snapshot.profile_id.clone(), + status: snapshot.status.as_str().to_string(), + level_index: snapshot.level_index, + clears_done: snapshot.clears_done, + snapshot_json: to_json_string(snapshot), + started_at_ms: snapshot.started_at_ms as i64, + finished_at_ms: snapshot + .finished_at_ms + .map(|value| value as i64) + .unwrap_or(0), + created_at, + updated_at, + } +} + +fn increment_work_play_count( + ctx: &ReducerContext, + row: &PuzzleClearWorkProfileRow, + played_at_ms: i64, +) { + replace_work( + ctx, + row, + PuzzleClearWorkProfileRow { + play_count: row.play_count.saturating_add(1), + updated_at: Timestamp::from_micros_since_unix_epoch(played_at_ms.saturating_mul(1000)), + ..clone_work(row) + }, + ); +} + +fn insert_event( + ctx: &ReducerContext, + event_id: String, + owner_user_id: String, + profile_id: String, + run_id: String, + event_type: &str, + result: Option, + occurred_at_ms: i64, +) { + let event_id = clean_optional(&event_id).unwrap_or_else(|| { + format!( + "puzzle-clear-event-{}-{}-{}", + run_id, event_type, occurred_at_ms + ) + }); + if ctx + .db + .puzzle_clear_event() + .event_id() + .find(&event_id) + .is_some() + { + return; + } + ctx.db.puzzle_clear_event().insert(PuzzleClearEventRow { + event_id, + owner_user_id, + profile_id, + run_id, + event_type: event_type.to_string(), + result: result.unwrap_or_default(), + occurred_at: Timestamp::from_micros_since_unix_epoch(occurred_at_ms.saturating_mul(1000)), + }); +} + +fn insert_terminal_runtime_event_if_needed( + ctx: &ReducerContext, + previous: &DomainRunSnapshot, + next: &DomainRunSnapshot, + owner_user_id: String, + occurred_at_ms: i64, +) { + if previous.status == next.status { + return; + } + let event_type = match next.status { + module_puzzle_clear::PuzzleClearRunStatus::LevelCleared => { + Some(PUZZLE_CLEAR_EVENT_LEVEL_COMPLETED) + } + module_puzzle_clear::PuzzleClearRunStatus::Finished => { + Some(PUZZLE_CLEAR_EVENT_RUN_FINISHED) + } + module_puzzle_clear::PuzzleClearRunStatus::LevelFailed => { + Some(PUZZLE_CLEAR_EVENT_LEVEL_FAILED) + } + module_puzzle_clear::PuzzleClearRunStatus::Playing => None, + }; + let Some(event_type) = event_type else { + return; + }; + insert_event( + ctx, + format!("{}:{}:{}", next.run_id, event_type, next.level_index), + owner_user_id, + next.profile_id.clone(), + next.run_id.clone(), + event_type, + Some(runtime_event_result(previous, next, occurred_at_ms)), + occurred_at_ms, + ); +} + +fn runtime_event_result( + previous: &DomainRunSnapshot, + next: &DomainRunSnapshot, + occurred_at_ms: i64, +) -> String { + let elapsed_ms = occurred_at_ms + .max(0) + .saturating_sub(next.level_started_at_ms as i64); + json!({ + "status": next.status.as_str(), + "levelIndex": next.level_index, + "clearsDone": next.clears_done, + "clearDelta": next.clears_done.saturating_sub(previous.clears_done), + "elapsedMs": elapsed_ms, + }) + .to_string() +} + +fn is_publish_ready(row: &PuzzleClearWorkProfileRow) -> bool { + !row.work_title.trim().is_empty() + && !row.atlas_asset_json.trim().is_empty() + && !row.pattern_groups_json.trim().is_empty() + && !row.card_assets_json.trim().is_empty() + && row.generation_status == PUZZLE_CLEAR_GENERATION_READY + && parse_json::(&row.atlas_asset_json) + .map(|asset| { + is_real_puzzle_clear_asset( + asset.asset_object_id.as_str(), + asset.image_object_key.as_str(), + asset.image_src.as_str(), + ) + }) + .unwrap_or(false) + && parse_json::>(&row.card_assets_json) + .map(|assets| { + !assets.is_empty() + && assets.iter().all(|asset| { + is_real_puzzle_clear_asset( + asset.asset_object_id.as_str(), + asset.image_object_key.as_str(), + asset.image_src.as_str(), + ) + }) + }) + .unwrap_or(false) +} + +fn is_real_puzzle_clear_asset( + asset_object_id: &str, + image_object_key: &str, + image_src: &str, +) -> bool { + asset_object_id.starts_with("assetobj_") + && image_object_key.starts_with("generated-puzzle-clear-assets/") + && image_src.starts_with("/generated-puzzle-clear-assets/") +} + +fn require_non_empty(value: &str, label: &str) -> Result<(), String> { + if value.trim().is_empty() { + Err(format!("{label} 不能为空")) + } else { + Ok(()) + } +} + +fn clean_optional(value: &str) -> Option { + let value = value.trim(); + if value.is_empty() { + None + } else { + Some(value.to_string()) + } +} + +fn clean_string(value: &str, fallback: &str) -> String { + clean_optional(value).unwrap_or_else(|| fallback.to_string()) +} + +fn parse_json(value: &str) -> Result +where + T: DeserializeOwned, +{ + serde_json::from_str(value).map_err(|error| error.to_string()) +} + +fn parse_json_or_default(value: &str) -> T +where + T: DeserializeOwned + Default, +{ + serde_json::from_str(value).unwrap_or_default() +} + +fn to_json_string(value: &T) -> String +where + T: Serialize, +{ + serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string()) +} + +fn session_result( + session: PuzzleClearAgentSessionSnapshot, +) -> PuzzleClearAgentSessionProcedureResult { + PuzzleClearAgentSessionProcedureResult { + ok: true, + session: Some(session), + error_message: None, + } +} + +fn session_error(message: String) -> PuzzleClearAgentSessionProcedureResult { + PuzzleClearAgentSessionProcedureResult { + ok: false, + session: None, + error_message: Some(message), + } +} + +fn work_result(work: PuzzleClearWorkSnapshot) -> PuzzleClearWorkProcedureResult { + PuzzleClearWorkProcedureResult { + ok: true, + work: Some(work), + error_message: None, + } +} + +fn work_error(message: String) -> PuzzleClearWorkProcedureResult { + PuzzleClearWorkProcedureResult { + ok: false, + work: None, + error_message: Some(message), + } +} + +fn run_result(run: PuzzleClearRuntimeSnapshot) -> PuzzleClearRunProcedureResult { + PuzzleClearRunProcedureResult { + ok: true, + run: Some(run), + error_message: None, + } +} + +fn run_error(message: String) -> PuzzleClearRunProcedureResult { + PuzzleClearRunProcedureResult { + ok: false, + run: None, + error_message: Some(message), + } +} + +fn clone_session(row: &PuzzleClearAgentSessionRow) -> PuzzleClearAgentSessionRow { + PuzzleClearAgentSessionRow { + session_id: row.session_id.clone(), + owner_user_id: row.owner_user_id.clone(), + status: row.status.clone(), + draft_json: row.draft_json.clone(), + published_profile_id: row.published_profile_id.clone(), + created_at: row.created_at, + updated_at: row.updated_at, + } +} + +fn clone_work(row: &PuzzleClearWorkProfileRow) -> PuzzleClearWorkProfileRow { + PuzzleClearWorkProfileRow { + profile_id: row.profile_id.clone(), + work_id: row.work_id.clone(), + owner_user_id: row.owner_user_id.clone(), + source_session_id: row.source_session_id.clone(), + author_display_name: row.author_display_name.clone(), + work_title: row.work_title.clone(), + work_description: row.work_description.clone(), + theme_prompt: row.theme_prompt.clone(), + generate_board_background: row.generate_board_background, + board_background_asset_json: row.board_background_asset_json.clone(), + board_background_prompt: row.board_background_prompt.clone(), + card_back_image_src: row.card_back_image_src.clone(), + atlas_asset_json: row.atlas_asset_json.clone(), + pattern_groups_json: row.pattern_groups_json.clone(), + card_assets_json: row.card_assets_json.clone(), + cover_image_src: row.cover_image_src.clone(), + generation_status: row.generation_status.clone(), + publication_status: row.publication_status.clone(), + play_count: row.play_count, + updated_at: row.updated_at, + published_at: row.published_at, + visible: row.visible, + } +} + +fn clone_run(row: &PuzzleClearRuntimeRunRow) -> PuzzleClearRuntimeRunRow { + PuzzleClearRuntimeRunRow { + run_id: row.run_id.clone(), + owner_user_id: row.owner_user_id.clone(), + profile_id: row.profile_id.clone(), + status: row.status.clone(), + level_index: row.level_index, + clears_done: row.clears_done, + snapshot_json: row.snapshot_json.clone(), + started_at_ms: row.started_at_ms, + finished_at_ms: row.finished_at_ms, + created_at: row.created_at, + updated_at: row.updated_at, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn puzzle_clear_publish_ready_rejects_placeholder_assets() { + let now = Timestamp::from_micros_since_unix_epoch(1_780_000_000_000_000); + let groups = default_pattern_groups(); + let atlas = default_atlas_asset("puzzle-clear-profile-placeholder", "占位主题"); + let cards = default_card_assets("puzzle-clear-profile-placeholder", &groups); + let row = PuzzleClearWorkProfileRow { + profile_id: "puzzle-clear-profile-placeholder".to_string(), + work_id: "puzzle-clear-profile-placeholder".to_string(), + owner_user_id: "user-1".to_string(), + source_session_id: "puzzle-clear-session-placeholder".to_string(), + author_display_name: "拼消消玩家".to_string(), + work_title: "占位拼消消".to_string(), + work_description: String::new(), + theme_prompt: "占位主题".to_string(), + generate_board_background: false, + board_background_asset_json: String::new(), + board_background_prompt: None, + card_back_image_src: PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC.to_string(), + atlas_asset_json: to_json_string(&atlas), + pattern_groups_json: to_json_string(&groups), + card_assets_json: to_json_string(&cards), + cover_image_src: atlas.image_src, + generation_status: PUZZLE_CLEAR_GENERATION_READY.to_string(), + publication_status: PUZZLE_CLEAR_PUBLICATION_DRAFT.to_string(), + play_count: 0, + updated_at: now, + published_at: None, + visible: true, + }; + + assert!(!is_publish_ready(&row)); + } +} diff --git a/server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs b/server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs new file mode 100644 index 00000000..bdae388a --- /dev/null +++ b/server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs @@ -0,0 +1,88 @@ +use crate::*; + +const WORK_VISIBLE_DEFAULT: bool = true; + +#[spacetimedb::table( + accessor = puzzle_clear_agent_session, + index(accessor = by_puzzle_clear_agent_session_owner_user_id, btree(columns = [owner_user_id])) +)] +pub struct PuzzleClearAgentSessionRow { + #[primary_key] + pub(crate) session_id: String, + pub(crate) owner_user_id: String, + pub(crate) status: String, + pub(crate) draft_json: String, + pub(crate) published_profile_id: String, + pub(crate) created_at: Timestamp, + pub(crate) updated_at: Timestamp, +} + +#[spacetimedb::table( + accessor = puzzle_clear_work_profile, + index(accessor = by_puzzle_clear_work_owner_user_id, btree(columns = [owner_user_id])), + index(accessor = by_puzzle_clear_work_publication_status, btree(columns = [publication_status])) +)] +pub struct PuzzleClearWorkProfileRow { + #[primary_key] + pub(crate) profile_id: String, + pub(crate) work_id: String, + pub(crate) owner_user_id: String, + pub(crate) source_session_id: String, + pub(crate) author_display_name: String, + pub(crate) work_title: String, + pub(crate) work_description: String, + pub(crate) theme_prompt: String, + pub(crate) generate_board_background: bool, + pub(crate) board_background_asset_json: String, + #[default(None::)] + pub(crate) board_background_prompt: Option, + pub(crate) card_back_image_src: String, + pub(crate) atlas_asset_json: String, + pub(crate) pattern_groups_json: String, + pub(crate) card_assets_json: String, + pub(crate) cover_image_src: String, + pub(crate) generation_status: String, + pub(crate) publication_status: String, + pub(crate) play_count: u32, + pub(crate) updated_at: Timestamp, + pub(crate) published_at: Option, + // 中文注释:后台可见性开关,隐藏后不进入公开列表。 + #[default(WORK_VISIBLE_DEFAULT)] + pub(crate) visible: bool, +} + +#[spacetimedb::table( + accessor = puzzle_clear_runtime_run, + index(accessor = by_puzzle_clear_run_owner_user_id, btree(columns = [owner_user_id])), + index(accessor = by_puzzle_clear_run_profile_id, btree(columns = [profile_id])) +)] +pub struct PuzzleClearRuntimeRunRow { + #[primary_key] + pub(crate) run_id: String, + pub(crate) owner_user_id: String, + pub(crate) profile_id: String, + pub(crate) status: String, + pub(crate) level_index: u32, + pub(crate) clears_done: u32, + pub(crate) snapshot_json: String, + pub(crate) started_at_ms: i64, + pub(crate) finished_at_ms: i64, + pub(crate) created_at: Timestamp, + pub(crate) updated_at: Timestamp, +} + +#[spacetimedb::table( + accessor = puzzle_clear_event, + index(accessor = by_puzzle_clear_event_profile_id, btree(columns = [profile_id])), + index(accessor = by_puzzle_clear_event_run_id, btree(columns = [run_id])) +)] +pub struct PuzzleClearEventRow { + #[primary_key] + pub(crate) event_id: String, + pub(crate) owner_user_id: String, + pub(crate) profile_id: String, + pub(crate) run_id: String, + pub(crate) event_type: String, + pub(crate) result: String, + pub(crate) occurred_at: Timestamp, +} diff --git a/server-rs/crates/spacetime-module/src/puzzle_clear/types.rs b/server-rs/crates/spacetime-module/src/puzzle_clear/types.rs new file mode 100644 index 00000000..4e3a12ae --- /dev/null +++ b/server-rs/crates/spacetime-module/src/puzzle_clear/types.rs @@ -0,0 +1,304 @@ +use crate::*; +use serde::{Deserialize, Serialize}; + +pub const PUZZLE_CLEAR_TEMPLATE_ID: &str = "puzzle-clear"; +pub const PUZZLE_CLEAR_TEMPLATE_NAME: &str = "拼消消"; +pub const PUZZLE_CLEAR_PUBLICATION_DRAFT: &str = "draft"; +pub const PUZZLE_CLEAR_PUBLICATION_PUBLISHED: &str = "published"; +pub const PUZZLE_CLEAR_GENERATION_DRAFT: &str = "draft"; +pub const PUZZLE_CLEAR_GENERATION_READY: &str = "ready"; +pub const PUZZLE_CLEAR_GENERATION_FAILED: &str = "failed"; +pub const PUZZLE_CLEAR_CARD_BACK_IMAGE_SRC: &str = "/creation-type-references/puzzle.webp"; +pub const PUZZLE_CLEAR_EVENT_RUN_STARTED: &str = "run-started"; +pub const PUZZLE_CLEAR_EVENT_SWAP: &str = "swap"; +pub const PUZZLE_CLEAR_EVENT_RETRY_LEVEL: &str = "retry-level"; +pub const PUZZLE_CLEAR_EVENT_NEXT_LEVEL: &str = "next-level"; +pub const PUZZLE_CLEAR_EVENT_TIME_UP: &str = "time-up"; +pub const PUZZLE_CLEAR_EVENT_LEVEL_COMPLETED: &str = "level-completed"; +pub const PUZZLE_CLEAR_EVENT_RUN_FINISHED: &str = "run-finished"; +pub const PUZZLE_CLEAR_EVENT_LEVEL_FAILED: &str = "level-failed"; + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearAgentSessionCreateInput { + pub session_id: String, + pub owner_user_id: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: Option, + pub board_background_prompt: String, + pub created_at_micros: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearAgentSessionGetInput { + pub session_id: String, + pub owner_user_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearDraftCompileInput { + pub session_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: Option, + pub board_background_prompt: String, + pub atlas_asset_json: Option, + pub pattern_groups_json: Option, + pub card_assets_json: Option, + pub generation_status: Option, + pub compiled_at_micros: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearWorkUpdateInput { + pub profile_id: String, + pub owner_user_id: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset_json: Option, + pub board_background_prompt: String, + pub updated_at_micros: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearWorkPublishInput { + pub profile_id: String, + pub owner_user_id: String, + pub published_at_micros: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearWorksListInput { + pub owner_user_id: String, + pub published_only: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearWorkGetInput { + pub profile_id: String, + pub owner_user_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearRunStartInput { + pub run_id: String, + pub owner_user_id: String, + pub profile_id: String, + pub client_event_id: String, + pub started_at_ms: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearRunGetInput { + pub run_id: String, + pub owner_user_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearRunSwapInput { + pub run_id: String, + pub owner_user_id: String, + pub from_row: u32, + pub from_col: u32, + pub to_row: u32, + pub to_col: u32, + pub client_action_id: String, + pub swapped_at_ms: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearRunRetryLevelInput { + pub run_id: String, + pub owner_user_id: String, + pub client_action_id: String, + pub restarted_at_ms: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearRunNextLevelInput { + pub run_id: String, + pub owner_user_id: String, + pub client_action_id: String, + pub started_at_ms: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct PuzzleClearRunTimeUpInput { + pub run_id: String, + pub owner_user_id: String, + pub client_action_id: String, + pub occurred_at_ms: i64, +} + +#[derive(Clone, Debug, PartialEq, SpacetimeType)] +pub struct PuzzleClearAgentSessionProcedureResult { + pub ok: bool, + pub session: Option, + pub error_message: Option, +} + +#[derive(Clone, Debug, PartialEq, SpacetimeType)] +pub struct PuzzleClearWorkProcedureResult { + pub ok: bool, + pub work: Option, + pub error_message: Option, +} + +#[derive(Clone, Debug, PartialEq, SpacetimeType)] +pub struct PuzzleClearWorksProcedureResult { + pub ok: bool, + pub items: Vec, + pub error_message: Option, +} + +#[derive(Clone, Debug, PartialEq, SpacetimeType)] +pub struct PuzzleClearRunProcedureResult { + pub ok: bool, + pub run: Option, + pub error_message: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearImageAssetSnapshot { + pub asset_id: String, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub generation_provider: String, + pub prompt: String, + pub width: u32, + pub height: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearPatternGroupSnapshot { + pub group_id: String, + pub shape: String, + pub width: u32, + pub height: u32, + pub atlas_x: u32, + pub atlas_y: u32, + pub atlas_width: u32, + pub atlas_height: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearCardAssetSnapshot { + pub card_id: String, + pub group_id: String, + pub shape: String, + pub orientation: String, + pub part_x: u32, + pub part_y: u32, + pub image_src: String, + pub image_object_key: String, + pub asset_object_id: String, + pub source_atlas_cell: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearDraftSnapshot { + pub template_id: String, + pub template_name: String, + pub profile_id: Option, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + #[serde(default)] + pub board_background_prompt: String, + pub card_back_image_src: Option, + pub atlas_asset: Option, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub generation_status: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearAgentSessionSnapshot { + pub session_id: String, + pub owner_user_id: String, + pub status: String, + pub draft: Option, + pub published_profile_id: Option, + pub created_at_micros: i64, + pub updated_at_micros: i64, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearWorkSnapshot { + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub source_session_id: String, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub theme_prompt: String, + pub generate_board_background: bool, + pub board_background_asset: Option, + #[serde(default)] + pub board_background_prompt: String, + pub card_back_image_src: Option, + pub atlas_asset: PuzzleClearImageAssetSnapshot, + pub pattern_groups: Vec, + pub card_assets: Vec, + pub cover_image_src: Option, + pub publication_status: String, + pub publish_ready: bool, + pub play_count: u32, + pub generation_status: String, + pub updated_at_micros: i64, + pub published_at_micros: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearBoardCellSnapshot { + pub row: u32, + pub col: u32, + pub card: Option, + pub locked_group_id: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearBoardSnapshot { + pub rows: u32, + pub cols: u32, + pub cells: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)] +#[serde(rename_all = "camelCase")] +pub struct PuzzleClearRuntimeSnapshot { + pub run_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub status: String, + pub level_index: u32, + pub clears_done: u32, + pub target_clears: u32, + pub level_duration_seconds: u32, + pub level_started_at_ms: u64, + pub board: PuzzleClearBoardSnapshot, + pub ready_columns: Vec>, + pub started_at_ms: u64, + pub finished_at_ms: Option, +} diff --git a/server-rs/crates/spacetime-module/src/wooden_fish.rs b/server-rs/crates/spacetime-module/src/wooden_fish.rs index dbfc0693..bf0e0454 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish.rs @@ -202,6 +202,25 @@ pub fn list_wooden_fish_works( } } +#[spacetimedb::procedure] +pub fn delete_wooden_fish_work( + ctx: &mut ProcedureContext, + input: WoodenFishWorkDeleteInput, +) -> WoodenFishWorksProcedureResult { + match ctx.try_with_tx(|tx| delete_wooden_fish_work_tx(tx, input.clone())) { + Ok(items) => WoodenFishWorksProcedureResult { + ok: true, + items, + error_message: None, + }, + Err(message) => WoodenFishWorksProcedureResult { + ok: false, + items: Vec::new(), + error_message: Some(message), + }, + } +} + #[spacetimedb::procedure] pub fn start_wooden_fish_run( ctx: &mut ProcedureContext, @@ -598,6 +617,62 @@ fn list_wooden_fish_works_tx( .collect() } +fn delete_wooden_fish_work_tx( + ctx: &ReducerContext, + input: WoodenFishWorkDeleteInput, +) -> Result, String> { + let work = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?; + ctx.db + .wooden_fish_work_profile() + .profile_id() + .delete(&work.profile_id); + if !work.source_session_id.trim().is_empty() { + if let Some(session) = ctx + .db + .wooden_fish_agent_session() + .session_id() + .find(&work.source_session_id) + .filter(|session| session.owner_user_id == input.owner_user_id) + { + ctx.db + .wooden_fish_agent_session() + .session_id() + .delete(&session.session_id); + } + } + for run in ctx + .db + .wooden_fish_runtime_run() + .by_wooden_fish_run_profile_id() + .filter(input.profile_id.as_str()) + .collect::>() + { + ctx.db + .wooden_fish_runtime_run() + .run_id() + .delete(&run.run_id); + } + for event in ctx + .db + .wooden_fish_event() + .by_wooden_fish_event_profile_id() + .filter(input.profile_id.as_str()) + .collect::>() + { + ctx.db + .wooden_fish_event() + .event_id() + .delete(&event.event_id); + } + list_wooden_fish_works_tx( + ctx, + WoodenFishWorksListInput { + owner_user_id: input.owner_user_id, + published_only: false, + }, + ) +} + fn start_wooden_fish_run_tx( ctx: &ReducerContext, input: WoodenFishRunStartInput, @@ -1363,6 +1438,17 @@ mod tests { ); } + #[test] + fn wooden_fish_delete_input_carries_owner_and_profile() { + let input = WoodenFishWorkDeleteInput { + profile_id: "wooden-fish-profile-1".to_string(), + owner_user_id: "user-1".to_string(), + }; + + assert_eq!(input.profile_id, "wooden-fish-profile-1"); + assert_eq!(input.owner_user_id, "user-1"); + } + fn published_ready_work_without_back_button() -> WoodenFishWorkProfileRow { let now = Timestamp::from_micros_since_unix_epoch(1_770_000_000_000_000); WoodenFishWorkProfileRow { diff --git a/server-rs/crates/spacetime-module/src/wooden_fish/types.rs b/server-rs/crates/spacetime-module/src/wooden_fish/types.rs index 2ad5cb1b..50b5580b 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish/types.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish/types.rs @@ -84,6 +84,12 @@ pub struct WoodenFishWorkPublishInput { pub published_at_micros: i64, } +#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] +pub struct WoodenFishWorkDeleteInput { + pub profile_id: String, + pub owner_user_id: String, +} + #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct WoodenFishWorksListInput { pub owner_user_id: String, diff --git a/src/components/CustomWorldCoverArtwork.tsx b/src/components/CustomWorldCoverArtwork.tsx index 4a9205f1..17911f06 100644 --- a/src/components/CustomWorldCoverArtwork.tsx +++ b/src/components/CustomWorldCoverArtwork.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react'; +import React, { type ReactNode } from 'react'; import type { CustomWorldCoverRenderMode } from '../services/customWorldCover'; import { ResolvedAssetImage } from './ResolvedAssetImage'; diff --git a/src/components/CustomWorldGenerationView.test.tsx b/src/components/CustomWorldGenerationView.test.tsx index 4b9abbdd..a2504917 100644 --- a/src/components/CustomWorldGenerationView.test.tsx +++ b/src/components/CustomWorldGenerationView.test.tsx @@ -74,6 +74,12 @@ describe('CustomWorldGenerationView', () => { expect((container.firstChild as HTMLElement).className).toContain( 'z-[1]', ); + expect((container.firstChild as HTMLElement).className).toContain( + 'overflow-hidden', + ); + expect((container.firstChild as HTMLElement).className).not.toContain( + 'overflow-y-auto', + ); const pageVideo = screen.getByTestId( 'generation-page-background-video', @@ -114,6 +120,14 @@ describe('CustomWorldGenerationView', () => { expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain( 'bg-white/58', ); + expect( + screen.getByTestId('generation-hero-wait-card').parentElement + ?.className, + ).toContain('mt-3'); + expect( + screen.getByTestId('generation-hero-wait-card').parentElement + ?.className, + ).toContain('px-0'); expect(screen.getByText('预计等待').className).toContain('text-[9px]'); expect(screen.getByText('已耗时').className).toContain('text-[9px]'); expect(screen.getByText('预计等待').parentElement?.className).toContain( @@ -231,6 +245,10 @@ describe('CustomWorldGenerationView', () => { expect( screen.getByTestId('generation-current-step-card').className, ).toContain('bg-white/58'); + expect( + screen.getByTestId('generation-current-step-card').parentElement + ?.className, + ).toContain('mt-5'); expect( screen.getByRole('progressbar', { name: '编译草稿 进度' }), ).toBeTruthy(); @@ -238,10 +256,11 @@ describe('CustomWorldGenerationView', () => { expect(screen.queryByText('写回结果')).toBeNull(); expect(screen.queryByText('当前批次')).toBeNull(); expect(screen.queryByText('正在整理当前设定步骤')).toBeNull(); + expect(screen.queryByText('竖屏生成题材')).toBeNull(); }, ); - test('keeps the setting information panel as compact information cards', () => { + test('does not render setting information cards on generation pages', () => { render( { backLabel="返回创作中心" settingDescription={null} settingActionLabel={null} - settingTitle="当前大鱼吃小鱼信息" progressTitle="大鱼吃小鱼草稿生成进度" />, ); - expect(screen.getByText('当前大鱼吃小鱼信息')).toBeTruthy(); - expect(screen.getByText('当前大鱼吃小鱼信息').className).toContain('text-[13px]'); - expect(screen.getByText('题材')).toBeTruthy(); - expect(screen.getByText('题材').className).toContain('text-[9px]'); - expect(screen.getByText('火锅')).toBeTruthy(); - expect(screen.getByText('火锅').className).toContain('text-[13px]'); - expect(screen.getByText('素材数量')).toBeTruthy(); - expect(screen.getByText('20 种素材')).toBeTruthy(); + expect(screen.queryByText('当前大鱼吃小鱼信息')).toBeNull(); + expect(screen.queryByText('题材')).toBeNull(); + expect(screen.queryByText('火锅')).toBeNull(); + expect(screen.queryByText('素材数量')).toBeNull(); + expect(screen.queryByText('20 种素材')).toBeNull(); expect(screen.queryByText('大鱼吃小鱼题材')).toBeNull(); expect(screen.getByTestId('generation-page-background-video')).toBeTruthy(); }); diff --git a/src/components/CustomWorldGenerationView.tsx b/src/components/CustomWorldGenerationView.tsx index 55efe604..00ad0f17 100644 --- a/src/components/CustomWorldGenerationView.tsx +++ b/src/components/CustomWorldGenerationView.tsx @@ -97,33 +97,18 @@ function resolveCurrentGenerationStep( ); } -function buildFallbackRenderKey( - value: string | null | undefined, - fallback: string, -) { - const normalizedValue = value?.trim(); - return normalizedValue ? normalizedValue : fallback; -} - export function CustomWorldGenerationView({ - settingText, - anchorEntries = [], progress, isGenerating, onBack, - onEditSetting, onRetry, onInterrupt, backLabel = '返回', - settingActionLabel = '修改设定', retryLabel = '重新开始生成', interruptLabel = '中断世界生成', - settingTitle = '玩家设定', - settingDescription = '这段文本会直接驱动本轮世界框架、角色与场景生成。', progressTitle = '生成进度', activeBadgeLabel = '世界建设中', idleBadgeLabel = '等待操作', - structuredEmptyText = '正在整理当前设定结构,请稍后。', hideBatchModule = false, }: CustomWorldGenerationViewProps) { void hideBatchModule; @@ -138,12 +123,6 @@ export function CustomWorldGenerationView({ : isGenerating ? '进行中' : '待处理'; - const hasStructuredAnchors = anchorEntries.length > 0; - // 允许不同生成场景按需隐藏第二模块的说明和次级返回动作。 - const normalizedSettingActionLabel = settingActionLabel?.trim() ?? ''; - const normalizedSettingDescription = settingDescription?.trim() ?? ''; - const hasSettingActionLabel = normalizedSettingActionLabel.length > 0; - const hasSettingDescription = normalizedSettingDescription.length > 0; const estimatedWaitText = progress?.estimatedRemainingMs != null ? formatDuration(progress.estimatedRemainingMs) @@ -153,11 +132,10 @@ export function CustomWorldGenerationView({ return (
-
+
-
+
-
+
{!isGenerating ? ( - <> - {hasSettingActionLabel ? ( - - ) : null} - - + ) : onInterrupt ? (
- -
-
-
-
- {settingTitle} -
- {hasSettingDescription ? ( -
- {normalizedSettingDescription} -
- ) : null} -
- {hasSettingActionLabel ? ( - - ) : null} -
- {hasStructuredAnchors ? ( -
- {anchorEntries.map((entry, index) => ( -
-
- {entry.label} -
-
- {entry.value} -
-
- ))} -
- ) : ( -
- {settingText || structuredEmptyText} -
- )} -
); diff --git a/src/components/GenerationProgressHero.tsx b/src/components/GenerationProgressHero.tsx index 76902b26..18e66567 100644 --- a/src/components/GenerationProgressHero.tsx +++ b/src/components/GenerationProgressHero.tsx @@ -133,7 +133,7 @@ export function GenerationProgressHero({ const ringFillDasharray = `${ringMetrics.progressLength.toFixed(2)} ${ringMetrics.circumference.toFixed(2)}`; return ( -
+
{title} {phaseLabel ? ` ${phaseLabel}` : ''} @@ -215,7 +215,7 @@ export function GenerationProgressHero({
-
+
{ expect(screen.queryByText('当前主题')).toBeNull(); expect(screen.queryByRole('button', { name: '退出登录' })).toBeNull(); expect(screen.queryByRole('button', { name: '退出全部设备' })).toBeNull(); + expect(screen.getByRole('button', { name: /主题设置/u })).toBeTruthy(); + expect(screen.getByRole('button', { name: /账号与安全/u })).toBeTruthy(); + expect(screen.queryByRole('button', { name: /主题外观/u })).toBeNull(); + expect(screen.queryByRole('button', { name: /账号信息/u })).toBeNull(); }); test('direct account entry does not render the settings shell as another dialog', () => { @@ -129,12 +136,53 @@ test('direct account entry does not render the settings shell as another dialog' ).toBeNull(); }); +test('account panel uses compact binding cards and keeps logout actions at the bottom', () => { + renderAccountModal({ entryMode: 'account' }); + + const accountDialog = screen.getByRole('dialog', { name: '账号信息' }); + expect(within(accountDialog).getByText('账号信息')).toBeTruthy(); + expect(within(accountDialog).queryByText('身份信息')).toBeNull(); + expect( + within(accountDialog).queryByText( + '统一查看身份、安全状态、登录设备与最近操作。', + ), + ).toBeNull(); + expect(within(accountDialog).queryByText('登录方式')).toBeNull(); + expect(within(accountDialog).getByText('绑定手机号')).toBeTruthy(); + expect(within(accountDialog).getByText('13800138000')).toBeTruthy(); + expect(within(accountDialog).queryByText('138****8000')).toBeNull(); + expect(within(accountDialog).getByText('绑定微信')).toBeTruthy(); + expect(within(accountDialog).getByText('微信旅人甲')).toBeTruthy(); + expect(within(accountDialog).queryByText('wx-openid-bind-001')).toBeNull(); + + const compactCards = accountDialog.querySelectorAll( + '[data-account-binding-card]', + ); + expect(compactCards).toHaveLength(2); + expect( + within(compactCards[0] as HTMLElement).getByRole('button', { + name: '更换手机号', + }), + ).toBeTruthy(); + expect( + within(compactCards[1] as HTMLElement).getByRole('button', { + name: '更换微信号', + }), + ).toBeTruthy(); + + const accountContent = + accountDialog.querySelector('[data-account-content]') ?? accountDialog; + expect( + accountContent.lastElementChild?.getAttribute('data-account-actions'), + ).toBe('true'); +}); + test('account actions open in independent panels instead of inline expansion', async () => { const user = userEvent.setup(); renderAccountModal(); - await user.click(screen.getByRole('button', { name: /账号信息/ })); + await user.click(screen.getByRole('button', { name: /账号与安全/ })); const accountDialog = screen.getByRole('dialog', { name: '账号信息' }); expect(accountDialog).toBeTruthy(); @@ -162,7 +210,7 @@ test('nested settings panels keep back navigation without an extra close action' renderAccountModal(); - await user.click(screen.getByRole('button', { name: /账号信息/ })); + await user.click(screen.getByRole('button', { name: /账号与安全/ })); const accountDialog = screen.getByRole('dialog', { name: '账号信息' }); const accountHeader = accountDialog.firstElementChild as HTMLElement | null; @@ -201,7 +249,7 @@ test('settings overlays move focus away from inert triggers and restore it on ba renderAccountModal(); - const accountTrigger = screen.getByRole('button', { name: /账号信息/ }); + const accountTrigger = screen.getByRole('button', { name: /账号与安全/ }); expect(document.activeElement).not.toBe(accountTrigger); await user.click(accountTrigger); @@ -283,7 +331,7 @@ test('account panel includes merged security devices and audit sections', async ], }); - await user.click(screen.getByRole('button', { name: /账号信息/ })); + await user.click(screen.getByRole('button', { name: /账号与安全/ })); const accountDialog = screen.getByRole('dialog', { name: '账号信息' }); expect(within(accountDialog).getByText('安全状态')).toBeTruthy(); @@ -324,7 +372,7 @@ test('current merged session group hides kick action and shows count', async () ], }); - await user.click(screen.getByRole('button', { name: /账号信息/ })); + await user.click(screen.getByRole('button', { name: /账号与安全/ })); const accountDialog = screen.getByRole('dialog', { name: '账号信息' }); expect(within(accountDialog).getByText('2 个会话')).toBeTruthy(); @@ -348,7 +396,7 @@ test('remote merged session group can be revoked with loading state', async () = revokingSessionIds: ['usess_remote'], }); - await user.click(screen.getByRole('button', { name: /账号信息/ })); + await user.click(screen.getByRole('button', { name: /账号与安全/ })); const accountDialog = screen.getByRole('dialog', { name: '账号信息' }); const revokeButton = within(accountDialog).getByRole('button', { @@ -373,7 +421,7 @@ test('remote session revoke passes the grouped session payload', async () => { onRevokeSession, }); - await user.click(screen.getByRole('button', { name: /账号信息/ })); + await user.click(screen.getByRole('button', { name: /账号与安全/ })); await user.click( within(screen.getByRole('dialog', { name: '账号信息' })).getByRole( 'button', diff --git a/src/components/auth/AccountModal.tsx b/src/components/auth/AccountModal.tsx index 45de37cb..213c2055 100644 --- a/src/components/auth/AccountModal.tsx +++ b/src/components/auth/AccountModal.tsx @@ -1,3 +1,4 @@ +import { ArrowLeft } from 'lucide-react'; import { type ReactNode, useCallback, @@ -65,8 +66,8 @@ const SETTINGS_SECTIONS: Array<{ label: string; detail: string; }> = [ - { id: 'appearance', label: '主题外观', detail: '亮暗主题' }, - { id: 'account', label: '账号信息', detail: '身份与安全' }, + { id: 'appearance', label: '主题设置', detail: '亮暗主题' }, + { id: 'account', label: '账号与安全', detail: '身份与设备' }, ]; const ACCOUNT_MODAL_MAX_HEIGHT = @@ -93,17 +94,6 @@ function normalizeSettingsSection( return null; } -function resolveLoginMethodLabel(loginMethod: AuthUser['loginMethod']) { - switch (loginMethod) { - case 'wechat': - return '微信登录'; - case 'phone': - return '手机号登录'; - default: - return '账号登录'; - } -} - function formatSessionTime(value: string) { const date = new Date(value); if (Number.isNaN(date.getTime())) { @@ -166,7 +156,7 @@ function OverlayPanel({ onClose, children, }: { - eyebrow: string; + eyebrow?: string; title: string; description?: string; action?: ReactNode; @@ -184,12 +174,16 @@ function OverlayPanel({ style={{ maxHeight: ACCOUNT_MODAL_MAX_HEIGHT }} onClick={(event) => event.stopPropagation()} > -
+
-
- {eyebrow} -
-
+ {eyebrow ? ( +
+ {eyebrow} +
+ ) : null} +
{title}
{description ? ( @@ -204,9 +198,10 @@ function OverlayPanel({ ) : ( @@ -446,17 +441,16 @@ export function AccountModal({ ? '正在同步平台设置...' : '平台设置已同步'; - const accountSummaryCards = [ - ['登录方式', resolveLoginMethodLabel(user.loginMethod)], - ['手机号', user.phoneNumberMasked || '未绑定'], - ['微信绑定', user.wechatBound ? '已绑定' : '未绑定'], - ] as const; + const boundPhoneNumber = + user.phoneNumber?.trim() || user.phoneNumberMasked || '未绑定'; + const boundWechatDisplayName = + user.wechatDisplayName?.trim() || (user.wechatBound ? '已绑定' : '未绑定'); const sectionSummaries: Record = { appearance: platformTheme === 'dark' ? '当前使用暗色主题。' : '当前使用亮色主题。', account: - user.phoneNumberMasked || user.wechatBound + user.phoneNumber || user.phoneNumberMasked || user.wechatBound ? '查看身份、安全状态、登录设备与操作记录。' : '查看账号绑定状态与安全记录。', }; @@ -524,7 +518,7 @@ export function AccountModal({ {activeSection === 'appearance' ? ( -
+
{accountNotice ? (
{accountNotice}
) : null} -
- {accountSummaryCards.map(([label, value]) => ( -
-
- {label} -
-
- {value} +
+
+
+
+ 绑定手机号
+
- ))} +
+ {boundPhoneNumber} +
+
+ +
+
+
+ 绑定微信 +
+ +
+
+ {boundWechatDisplayName} +
+
-
- - -
- -
+
-
+
登录密码
-
- 在独立面板中设置或修改账号密码。 -
-
+
-
- 更换手机号 -
-
- 在独立面板中输入新的手机号与验证码。 -
-
- -
-
- -
-
-
-
+
安全状态
-
- 查看当前生效中的账号保护与限制。 -
-
+
{loadingRiskBlocks ? (
正在读取安全状态... @@ -734,15 +709,12 @@ export function AccountModal({
-
-
+
+
-
+
登录设备
-
- 查看当前账号的设备会话与登录状态。 -
-
+
{loadingSessions ? (
正在读取当前登录设备... @@ -818,15 +790,12 @@ export function AccountModal({
-
-
+
+
-
+
操作记录
-
- 查看最近的账号登录与安全动作。 -
-
+
{loadingAuditLogs ? (
正在读取账号操作记录... @@ -873,6 +842,30 @@ export function AccountModal({ )}
+ +
+ + +
{isChangePhonePanelOpen ? ( diff --git a/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx b/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx index 30c423b2..a2f180dd 100644 --- a/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx +++ b/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx @@ -15,10 +15,6 @@ vi.mock('../../services/bark-battle-creation', () => ({ updateBarkBattleDraftConfig: vi.fn(), })); -vi.mock('./BarkBattlePreviewCard', () => ({ - BarkBattlePreviewCard: () =>
汪汪声浪预览
, -})); - const draft = { draftId: 'bark-battle-draft-1', workId: 'BB-12345678', @@ -61,6 +57,12 @@ describe('BarkBattleGeneratingView', () => { expect(container.firstChild).toBeTruthy(); expect((container.firstChild as HTMLElement).className).toContain('z-[1]'); + expect((container.firstChild as HTMLElement).className).toContain( + 'overflow-hidden', + ); + expect((container.firstChild as HTMLElement).className).not.toContain( + 'overflow-y-auto', + ); expect(screen.getByText('总进度')).toBeTruthy(); expect(screen.getByText('总进度').className).toContain('text-[9px]'); const pageVideo = screen.getByTestId( @@ -100,6 +102,14 @@ describe('BarkBattleGeneratingView', () => { expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain( 'bg-white/58', ); + expect( + screen.getByTestId('generation-hero-wait-card').parentElement + ?.className, + ).toContain('mt-3'); + expect( + screen.getByTestId('generation-hero-wait-card').parentElement + ?.className, + ).toContain('px-0'); expect(screen.getByText('预计等待').className).toContain('text-[9px]'); expect(screen.getByText('已耗时').className).toContain('text-[9px]'); expect(screen.getByText('预计等待').parentElement?.className).toContain( @@ -218,7 +228,13 @@ describe('BarkBattleGeneratingView', () => { expect( screen.getByTestId('generation-current-step-card').className, ).toContain('bg-white/58'); - expect(screen.getByText('预览信息').className).toContain('text-[13px]'); + expect( + screen.getByTestId('generation-current-step-card').parentElement + ?.className, + ).toContain('mt-5'); + expect(screen.queryByText('预览信息')).toBeNull(); + expect(screen.queryByText('汪汪声浪预览')).toBeNull(); + expect(screen.queryByText('霓虹公园擂台')).toBeNull(); expect(screen.queryByText('对手形象')).toBeNull(); expect(screen.queryByText('竞技背景')).toBeNull(); expect(onComplete).not.toHaveBeenCalled(); diff --git a/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx b/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx index 2a1a293f..90912c74 100644 --- a/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx +++ b/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx @@ -17,7 +17,6 @@ import { GenerationPageBackdrop, GenerationProgressHero, } from '../GenerationProgressHero'; -import { BarkBattlePreviewCard } from './BarkBattlePreviewCard'; type BarkBattleGeneratingViewProps = { draft: BarkBattleDraftConfig; @@ -355,54 +354,49 @@ export function BarkBattleGeneratingView({ }, [draft, onComplete, onError]); return ( -
+
-
-
- - - 生成中 - -
+
+ + + 生成中 + +
-
-
- +
+ + +
+ - -
- -
- - {error || primaryFailureMessage ? ( -
- {error ?? primaryFailureMessage} -
- ) : null}
-
-
- 预览信息 + + {error || primaryFailureMessage ? ( +
+ {error ?? primaryFailureMessage}
- -
+ ) : null}
diff --git a/src/components/common/CreativeAudioInputPanel.tsx b/src/components/common/CreativeAudioInputPanel.tsx index 84d87893..fba3c862 100644 --- a/src/components/common/CreativeAudioInputPanel.tsx +++ b/src/components/common/CreativeAudioInputPanel.tsx @@ -1,15 +1,11 @@ import { Mic, Pause, Upload } from 'lucide-react'; import { useRef, useState } from 'react'; -export type CreativeAudioAsset = { - assetId: string; - audioSrc: string; - audioObjectKey: string; - assetObjectId: string; - source: string; - prompt?: string | null; - durationMs?: number | null; -}; +import { + type CreativeAudioAsset, + readCreativeAudioFileAsAsset, +} from './creativeAudioFileAsset'; +import { trimLeadingSilenceFromRecordedAudioFile } from './creativeAudioSilenceTrim'; type CreativeAudioInputPanelProps = { disabled?: boolean; @@ -25,32 +21,6 @@ type CreativeAudioInputPanelProps = { ) => Promise; }; -export function readCreativeAudioFileAsAsset( - file: File, - source: 'uploaded' | 'recorded', -) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onerror = () => reject(new Error('音频读取失败,请重试。')); - reader.onload = () => { - if (typeof reader.result !== 'string') { - reject(new Error('音频读取失败,请重试。')); - return; - } - resolve({ - assetId: `local-${source}-${Date.now()}`, - audioSrc: reader.result, - audioObjectKey: '', - assetObjectId: '', - source, - prompt: file.name, - durationMs: null, - } as TAsset); - }; - reader.readAsDataURL(file); - }); -} - export function CreativeAudioInputPanel({ disabled = false, title, @@ -94,7 +64,8 @@ export function CreativeAudioInputPanel({ const file = new File([blob], buildRecordedFileName(), { type: blob.type, }); - void readFileAsAsset(file, 'recorded') + void trimLeadingSilenceFromRecordedAudioFile(file) + .then((trimmedFile) => readFileAsAsset(trimmedFile, 'recorded')) .then(onAssetChange) .catch((caughtError) => { onError( diff --git a/src/components/common/creativeAudioFileAsset.ts b/src/components/common/creativeAudioFileAsset.ts new file mode 100644 index 00000000..5775566c --- /dev/null +++ b/src/components/common/creativeAudioFileAsset.ts @@ -0,0 +1,35 @@ +export type CreativeAudioAsset = { + assetId: string; + audioSrc: string; + audioObjectKey: string; + assetObjectId: string; + source: string; + prompt?: string | null; + durationMs?: number | null; +}; + +export function readCreativeAudioFileAsAsset( + file: File, + source: 'uploaded' | 'recorded', +) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = () => reject(new Error('音频读取失败,请重试。')); + reader.onload = () => { + if (typeof reader.result !== 'string') { + reject(new Error('音频读取失败,请重试。')); + return; + } + resolve({ + assetId: `local-${source}-${Date.now()}`, + audioSrc: reader.result, + audioObjectKey: '', + assetObjectId: '', + source, + prompt: file.name, + durationMs: null, + } as TAsset); + }; + reader.readAsDataURL(file); + }); +} diff --git a/src/components/common/creativeAudioSilenceTrim.test.ts b/src/components/common/creativeAudioSilenceTrim.test.ts new file mode 100644 index 00000000..72c1d9fe --- /dev/null +++ b/src/components/common/creativeAudioSilenceTrim.test.ts @@ -0,0 +1,77 @@ +import { expect, test } from 'vitest'; + +import { + buildLeadingSilenceTrimmedWavBlob, + findFirstAudibleFrame, +} from './creativeAudioSilenceTrim'; + +function createAudioBufferStub( + channels: number[][], + sampleRate = 1000, +): AudioBuffer { + return { + length: channels[0]?.length ?? 0, + numberOfChannels: channels.length, + sampleRate, + duration: (channels[0]?.length ?? 0) / sampleRate, + getChannelData: (channel: number) => + new Float32Array(channels[channel] ?? []), + } as AudioBuffer; +} + +test('findFirstAudibleFrame skips leading frames that are silent across all channels', () => { + const buffer = createAudioBufferStub([ + [0, 0.003, -0.006, 0.012, 0.02], + [0, -0.004, 0.009, 0, 0], + ]); + + expect(findFirstAudibleFrame(buffer, 0.01)).toBe(3); +}); + +test('buildLeadingSilenceTrimmedWavBlob writes a wav that starts at the first audible frame', async () => { + const buffer = createAudioBufferStub( + [ + [0, 0, 0, 0.25, -0.5], + [0, 0, 0, -0.25, 0.5], + ], + 1000, + ); + + const blob = buildLeadingSilenceTrimmedWavBlob(buffer, { + silenceThreshold: 0.01, + minimumTrimDurationMs: 1, + }); + + expect(blob).not.toBeNull(); + expect(blob?.type).toBe('audio/wav'); + + const bytes = await blob!.arrayBuffer(); + const view = new DataView(bytes); + + expect(String.fromCharCode(...new Uint8Array(bytes, 0, 4))).toBe('RIFF'); + expect(String.fromCharCode(...new Uint8Array(bytes, 8, 4))).toBe('WAVE'); + expect(String.fromCharCode(...new Uint8Array(bytes, 36, 4))).toBe('data'); + expect(view.getUint32(40, true)).toBe(8); + expect(view.getInt16(44, true)).toBeCloseTo(8191, -1); + expect(view.getInt16(46, true)).toBeCloseTo(-8192, -1); + expect(view.getInt16(48, true)).toBeCloseTo(-16384, -1); + expect(view.getInt16(50, true)).toBeCloseTo(16383, -1); +}); + +test('buildLeadingSilenceTrimmedWavBlob keeps the original recording when no leading silence is removable', () => { + const startsImmediately = createAudioBufferStub([[0.2, 0.1, 0.05]], 1000); + const allSilent = createAudioBufferStub([[0, 0.001, -0.001]], 1000); + + expect( + buildLeadingSilenceTrimmedWavBlob(startsImmediately, { + silenceThreshold: 0.01, + minimumTrimDurationMs: 1, + }), + ).toBeNull(); + expect( + buildLeadingSilenceTrimmedWavBlob(allSilent, { + silenceThreshold: 0.01, + minimumTrimDurationMs: 1, + }), + ).toBeNull(); +}); diff --git a/src/components/common/creativeAudioSilenceTrim.ts b/src/components/common/creativeAudioSilenceTrim.ts new file mode 100644 index 00000000..60e0b764 --- /dev/null +++ b/src/components/common/creativeAudioSilenceTrim.ts @@ -0,0 +1,190 @@ +type BrowserAudioGlobal = typeof globalThis & { + webkitAudioContext?: typeof AudioContext; +}; + +export type LeadingSilenceTrimOptions = { + silenceThreshold?: number; + minimumTrimDurationMs?: number; +}; + +const DEFAULT_SILENCE_THRESHOLD = 0.01; +const DEFAULT_MINIMUM_TRIM_DURATION_MS = 20; +const WAV_HEADER_BYTE_LENGTH = 44; +const WAV_BITS_PER_SAMPLE = 16; +const WAV_BYTES_PER_SAMPLE = WAV_BITS_PER_SAMPLE / 8; + +export function findFirstAudibleFrame( + buffer: AudioBuffer, + silenceThreshold = DEFAULT_SILENCE_THRESHOLD, +) { + const threshold = Math.max(0, silenceThreshold); + + for (let frameIndex = 0; frameIndex < buffer.length; frameIndex += 1) { + for ( + let channelIndex = 0; + channelIndex < buffer.numberOfChannels; + channelIndex += 1 + ) { + const channelData = buffer.getChannelData(channelIndex); + if (Math.abs(channelData[frameIndex] ?? 0) > threshold) { + return frameIndex; + } + } + } + + return null; +} + +export function buildLeadingSilenceTrimmedWavBlob( + buffer: AudioBuffer, + options: LeadingSilenceTrimOptions = {}, +) { + const silenceThreshold = + options.silenceThreshold ?? DEFAULT_SILENCE_THRESHOLD; + const minimumTrimDurationMs = + options.minimumTrimDurationMs ?? DEFAULT_MINIMUM_TRIM_DURATION_MS; + const firstAudibleFrame = findFirstAudibleFrame(buffer, silenceThreshold); + + if (firstAudibleFrame === null) { + return null; + } + + const minimumTrimFrames = Math.max( + 1, + Math.round((buffer.sampleRate * minimumTrimDurationMs) / 1000), + ); + if (firstAudibleFrame < minimumTrimFrames) { + return null; + } + + const frameCount = buffer.length - firstAudibleFrame; + if (frameCount <= 0) { + return null; + } + + return encodeAudioBufferSectionToWavBlob( + buffer, + firstAudibleFrame, + frameCount, + ); +} + +export async function trimLeadingSilenceFromRecordedAudioFile( + file: File, + options: LeadingSilenceTrimOptions = {}, +) { + try { + const decodedBuffer = await decodeRecordedAudioFile(file); + if (!decodedBuffer) { + return file; + } + + const trimmedBlob = buildLeadingSilenceTrimmedWavBlob( + decodedBuffer, + options, + ); + if (!trimmedBlob) { + return file; + } + + return new File([trimmedBlob], buildTrimmedAudioFileName(file.name), { + type: trimmedBlob.type, + lastModified: Date.now(), + }); + } catch { + // 录音裁剪只是体验优化,浏览器解码失败时必须保留用户刚录好的原始文件。 + return file; + } +} + +function getAudioContextConstructor() { + const audioGlobal = globalThis as BrowserAudioGlobal; + return audioGlobal.AudioContext ?? audioGlobal.webkitAudioContext ?? null; +} + +async function decodeRecordedAudioFile(file: File) { + const AudioContextConstructor = getAudioContextConstructor(); + if (!AudioContextConstructor) { + return null; + } + + const context = new AudioContextConstructor(); + try { + const bytes = await file.arrayBuffer(); + return await context.decodeAudioData(bytes.slice(0)); + } finally { + void context.close(); + } +} + +function encodeAudioBufferSectionToWavBlob( + buffer: AudioBuffer, + startFrame: number, + frameCount: number, +) { + // MediaRecorder 输出格式不稳定;解码后统一写成 WAV,避免再依赖浏览器重新编码。 + const channelCount = Math.max(1, buffer.numberOfChannels); + const dataByteLength = + frameCount * channelCount * WAV_BYTES_PER_SAMPLE; + const output = new ArrayBuffer(WAV_HEADER_BYTE_LENGTH + dataByteLength); + const view = new DataView(output); + const channelData = Array.from({ length: channelCount }, (_value, index) => + index < buffer.numberOfChannels + ? buffer.getChannelData(index) + : new Float32Array(buffer.length), + ); + + writeAscii(view, 0, 'RIFF'); + view.setUint32(4, 36 + dataByteLength, true); + writeAscii(view, 8, 'WAVE'); + writeAscii(view, 12, 'fmt '); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + view.setUint16(22, channelCount, true); + view.setUint32(24, buffer.sampleRate, true); + view.setUint32( + 28, + buffer.sampleRate * channelCount * WAV_BYTES_PER_SAMPLE, + true, + ); + view.setUint16(32, channelCount * WAV_BYTES_PER_SAMPLE, true); + view.setUint16(34, WAV_BITS_PER_SAMPLE, true); + writeAscii(view, 36, 'data'); + view.setUint32(40, dataByteLength, true); + + let outputOffset = WAV_HEADER_BYTE_LENGTH; + for (let frameOffset = 0; frameOffset < frameCount; frameOffset += 1) { + const sourceFrame = startFrame + frameOffset; + for (let channelIndex = 0; channelIndex < channelCount; channelIndex += 1) { + const sample = channelData[channelIndex]?.[sourceFrame] ?? 0; + view.setInt16(outputOffset, toSignedPcm16(sample), true); + outputOffset += WAV_BYTES_PER_SAMPLE; + } + } + + return new Blob([output], { type: 'audio/wav' }); +} + +function toSignedPcm16(sample: number) { + const clamped = Math.max(-1, Math.min(1, sample)); + return clamped < 0 + ? Math.round(clamped * 0x8000) + : Math.round(clamped * 0x7fff); +} + +function writeAscii(view: DataView, offset: number, value: string) { + for (let index = 0; index < value.length; index += 1) { + view.setUint8(offset + index, value.charCodeAt(index)); + } +} + +function buildTrimmedAudioFileName(fileName: string) { + const normalizedName = fileName.trim(); + if (!normalizedName) { + return 'recorded-audio.wav'; + } + + return /\.[^.]+$/u.test(normalizedName) + ? normalizedName.replace(/\.[^.]+$/u, '.wav') + : `${normalizedName}.wav`; +} diff --git a/src/components/custom-world-home/CustomWorldCreationHub.tsx b/src/components/custom-world-home/CustomWorldCreationHub.tsx index 971a8f31..47066baa 100644 --- a/src/components/custom-world-home/CustomWorldCreationHub.tsx +++ b/src/components/custom-world-home/CustomWorldCreationHub.tsx @@ -6,6 +6,7 @@ import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contra import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject'; import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop'; import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks'; +import type { PuzzleClearWorkSummaryResponse } from '../../../packages/shared/src/contracts/puzzleClear'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { WoodenFishWorkSummaryResponse } from '../../../packages/shared/src/contracts/woodenFish'; import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime'; @@ -77,6 +78,9 @@ type CustomWorldCreationHubProps = { | ((item: WoodenFishWorkSummaryResponse) => void) | null; onDeleteWoodenFish?: ((item: WoodenFishWorkSummaryResponse) => void) | null; + puzzleClearItems?: PuzzleClearWorkSummaryResponse[]; + onOpenPuzzleClearDetail?: ((item: PuzzleClearWorkSummaryResponse) => void) | null; + onDeletePuzzleClear?: ((item: PuzzleClearWorkSummaryResponse) => void) | null; puzzleItems?: PuzzleWorkSummary[]; onOpenPuzzleDetail?: (item: PuzzleWorkSummary) => void; onDeletePuzzle?: ((item: PuzzleWorkSummary) => void) | null; @@ -193,6 +197,9 @@ export function CustomWorldCreationHub({ woodenFishItems = [], onOpenWoodenFishDetail = null, onDeleteWoodenFish = null, + puzzleClearItems = [], + onOpenPuzzleClearDetail = null, + onDeletePuzzleClear = null, puzzleItems = [], onOpenPuzzleDetail, onDeletePuzzle = null, @@ -228,6 +235,7 @@ export function CustomWorldCreationHub({ squareHoleItems: isSquareHoleCreationVisible ? squareHoleItems : [], jumpHopItems, woodenFishItems, + puzzleClearItems, puzzleItems, babyObjectMatchItems, barkBattleItems, @@ -239,6 +247,7 @@ export function CustomWorldCreationHub({ isSquareHoleCreationVisible && Boolean(onDeleteSquareHole), canDeleteJumpHop: Boolean(onDeleteJumpHop), canDeleteWoodenFish: Boolean(onDeleteWoodenFish), + canDeletePuzzleClear: Boolean(onDeletePuzzleClear), canDeletePuzzle: Boolean(onDeletePuzzle), canDeleteBabyObjectMatch: Boolean(onDeleteBabyObjectMatch), canDeleteBarkBattle: Boolean(onDeleteBarkBattle), @@ -256,6 +265,8 @@ export function CustomWorldCreationHub({ onDeleteJumpHop: onDeleteJumpHop ?? undefined, onOpenWoodenFishDetail: onOpenWoodenFishDetail ?? undefined, onDeleteWoodenFish: onDeleteWoodenFish ?? undefined, + onOpenPuzzleClearDetail: onOpenPuzzleClearDetail ?? undefined, + onDeletePuzzleClear: onDeletePuzzleClear ?? undefined, onOpenPuzzleDetail, onDeletePuzzle: onDeletePuzzle ?? undefined, onClaimPuzzlePointIncentive: onClaimPuzzlePointIncentive ?? undefined, @@ -284,6 +295,7 @@ export function CustomWorldCreationHub({ onDeleteVisualNovel, onDeleteJumpHop, onDeleteWoodenFish, + onDeletePuzzleClear, onClaimPuzzlePointIncentive, onOpenBigFishDetail, onOpenDraft, @@ -294,8 +306,10 @@ export function CustomWorldCreationHub({ onOpenSquareHoleDetail, onOpenVisualNovelDetail, onOpenWoodenFishDetail, + onOpenPuzzleClearDetail, onEnterPublished, getWorkState, + puzzleClearItems, puzzleItems, rpgLibraryEntries, onOpenJumpHopDetail, @@ -369,6 +383,9 @@ export function CustomWorldCreationHub({ case 'wooden-fish': onOpenWoodenFishDetail?.(item.source.item); return; + case 'puzzle-clear': + onOpenPuzzleClearDetail?.(item.source.item); + return; case 'rpg': if (item.status === 'draft') { onOpenDraft(item.source.item); diff --git a/src/components/custom-world-home/CustomWorldWorkCard.tsx b/src/components/custom-world-home/CustomWorldWorkCard.tsx index 2f3cc588..1c4da359 100644 --- a/src/components/custom-world-home/CustomWorldWorkCard.tsx +++ b/src/components/custom-world-home/CustomWorldWorkCard.tsx @@ -7,6 +7,7 @@ import { Trash2, } from 'lucide-react'; import { + default as React, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, @@ -62,6 +63,7 @@ const CREATION_WORK_KIND_FALLBACK_COVER: Record = 'square-hole': '/creation-type-references/square-hole.webp', 'jump-hop': '/creation-type-references/jump-hop.webp', 'wooden-fish': '/wooden-fish/default-hit-object.png', + 'puzzle-clear': '/creation-type-references/puzzle.webp', puzzle: '/creation-type-references/puzzle.webp', 'baby-object-match': '/creation-type-references/creative-agent.webp', 'bark-battle': '/creation-type-references/bark-battle.webp', diff --git a/src/components/custom-world-home/creationWorkShelf.test.ts b/src/components/custom-world-home/creationWorkShelf.test.ts index e0866d6d..d7e79efe 100644 --- a/src/components/custom-world-home/creationWorkShelf.test.ts +++ b/src/components/custom-world-home/creationWorkShelf.test.ts @@ -99,6 +99,47 @@ test('buildCreationWorkShelfItems maps wooden fish items with WF public code', ( expect(onOpenWoodenFishDetail).toHaveBeenCalledWith(woodenFishWork); }); +test('buildCreationWorkShelfItems maps puzzle clear items with PC public code', () => { + const onOpenPuzzleClearDetail = vi.fn(); + const puzzleClearWork = { + runtimeKind: 'puzzle-clear' as const, + workId: 'puzzle-clear-work-1', + profileId: 'puzzle-clear-profile-12345678', + ownerUserId: 'user-1', + sourceSessionId: 'puzzle-clear-session-1', + workTitle: '星港拼消消', + workDescription: '霓虹星港主题。', + themePrompt: '霓虹星港', + coverImageSrc: '/generated-puzzle-clear-assets/profile/atlas.png', + publicationStatus: 'published', + playCount: 6, + updatedAt: '2026-05-30T00:00:00.000Z', + publishedAt: '2026-05-30T00:00:00.000Z', + publishReady: true, + generationStatus: 'ready' as const, + }; + + const items = buildCreationWorkShelfItems({ + rpgItems: [], + bigFishItems: [], + puzzleItems: [], + puzzleClearItems: [puzzleClearWork], + onOpenPuzzleClearDetail, + }); + + items[0]?.actions.open(); + + expect(items).toHaveLength(1); + expect(items[0]?.kind).toBe('puzzle-clear'); + expect(items[0]?.status).toBe('published'); + expect(items[0]?.publicWorkCode).toBe('PC-12345678'); + expect(items[0]?.sharePath).toContain('/works/detail?work=PC-12345678'); + expect(items[0]?.openActionLabel).toBe('查看详情'); + expect(items[0]?.badges.some((badge) => badge.label === '拼消消')).toBe(true); + expect(items[0]?.metrics.find((metric) => metric.id === 'play-count')?.value).toBe(6); + expect(onOpenPuzzleClearDetail).toHaveBeenCalledWith(puzzleClearWork); +}); + test('buildCreationWorkShelfItems keeps published bark battle over duplicate draft', () => { const items = buildCreationWorkShelfItems({ rpgItems: [], diff --git a/src/components/custom-world-home/creationWorkShelf.ts b/src/components/custom-world-home/creationWorkShelf.ts index 1b12420f..1a75dc8a 100644 --- a/src/components/custom-world-home/creationWorkShelf.ts +++ b/src/components/custom-world-home/creationWorkShelf.ts @@ -3,6 +3,7 @@ import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/ import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent'; import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject'; import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks'; +import type { PuzzleClearWorkSummaryResponse } from '../../../packages/shared/src/contracts/puzzleClear'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime'; import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks'; @@ -17,6 +18,7 @@ import { buildBigFishPublicWorkCode, buildJumpHopPublicWorkCode, buildMatch3DPublicWorkCode, + buildPuzzleClearPublicWorkCode, buildPuzzlePublicWorkCode, buildSquareHolePublicWorkCode, buildVisualNovelPublicWorkCode, @@ -37,6 +39,7 @@ export type CreationWorkShelfKind = | 'square-hole' | 'jump-hop' | 'wooden-fish' + | 'puzzle-clear' | 'puzzle' | 'baby-object-match' | 'bark-battle' @@ -97,6 +100,10 @@ export type CreationWorkShelfSource = kind: 'wooden-fish'; item: WoodenFishWorkSummaryResponse; } + | { + kind: 'puzzle-clear'; + item: PuzzleClearWorkSummaryResponse; + } | { kind: 'puzzle'; item: PuzzleWorkSummary; @@ -165,6 +172,7 @@ export function buildCreationWorkShelfItems(params: { squareHoleItems?: SquareHoleWorkSummary[]; jumpHopItems?: JumpHopWorkSummaryResponse[]; woodenFishItems?: WoodenFishWorkSummaryResponse[]; + puzzleClearItems?: PuzzleClearWorkSummaryResponse[]; puzzleItems: PuzzleWorkSummary[]; babyObjectMatchItems?: BabyObjectMatchDraft[]; barkBattleItems?: BarkBattleWorkSummary[]; @@ -175,6 +183,7 @@ export function buildCreationWorkShelfItems(params: { canDeleteSquareHole?: boolean; canDeleteJumpHop?: boolean; canDeleteWoodenFish?: boolean; + canDeletePuzzleClear?: boolean; canDeletePuzzle?: boolean; canDeleteBabyObjectMatch?: boolean; canDeleteBarkBattle?: boolean; @@ -192,6 +201,8 @@ export function buildCreationWorkShelfItems(params: { onDeleteJumpHop?: (item: JumpHopWorkSummaryResponse) => void; onOpenWoodenFishDetail?: (item: WoodenFishWorkSummaryResponse) => void; onDeleteWoodenFish?: (item: WoodenFishWorkSummaryResponse) => void; + onOpenPuzzleClearDetail?: (item: PuzzleClearWorkSummaryResponse) => void; + onDeletePuzzleClear?: (item: PuzzleClearWorkSummaryResponse) => void; onOpenPuzzleDetail?: (item: PuzzleWorkSummary) => void; onDeletePuzzle?: (item: PuzzleWorkSummary) => void; onClaimPuzzlePointIncentive?: (item: PuzzleWorkSummary) => void; @@ -213,6 +224,7 @@ export function buildCreationWorkShelfItems(params: { squareHoleItems = [], jumpHopItems = [], woodenFishItems = [], + puzzleClearItems = [], puzzleItems, babyObjectMatchItems = [], barkBattleItems = [], @@ -223,6 +235,7 @@ export function buildCreationWorkShelfItems(params: { canDeleteSquareHole = false, canDeleteJumpHop = false, canDeleteWoodenFish = false, + canDeletePuzzleClear = false, canDeletePuzzle = false, canDeleteBabyObjectMatch = false, canDeleteBarkBattle = false, @@ -240,6 +253,8 @@ export function buildCreationWorkShelfItems(params: { onDeleteJumpHop, onOpenWoodenFishDetail, onDeleteWoodenFish, + onOpenPuzzleClearDetail, + onDeletePuzzleClear, onOpenPuzzleDetail, onDeletePuzzle, onClaimPuzzlePointIncentive, @@ -290,6 +305,12 @@ export function buildCreationWorkShelfItems(params: { onDelete: onDeleteWoodenFish, }), ), + ...puzzleClearItems.map((item) => + mapPuzzleClearWorkToShelfItem(item, canDeletePuzzleClear, { + onOpen: onOpenPuzzleClearDetail, + onDelete: onDeletePuzzleClear, + }), + ), ...puzzleItems.map((item) => mapPuzzleWorkToShelfItem(item, canDeletePuzzle, { onOpen: onOpenPuzzleDetail, @@ -903,6 +924,56 @@ function mapWoodenFishWorkToShelfItem( }; } +function mapPuzzleClearWorkToShelfItem( + item: PuzzleClearWorkSummaryResponse, + canDelete: boolean, + adapter: WorkShelfAdapter, +): CreationWorkShelfItem { + const status = item.publicationStatus === 'published' ? 'published' : 'draft'; + const publicWorkCode = + status === 'published' + ? buildPuzzleClearPublicWorkCode(item.profileId) + : null; + const title = item.workTitle.trim() || '拼消消'; + const summary = + item.workDescription.trim() || (status === 'draft' ? '未填写作品描述' : ''); + + return { + id: item.workId, + kind: 'puzzle-clear', + status, + title, + summary, + authorDisplayName: resolveAuthorDisplayName(item), + updatedAt: item.updatedAt, + coverImageSrc: normalizeCoverImageSrc(item.coverImageSrc), + coverRenderMode: 'image', + coverCharacterImageSrcs: [], + publicWorkCode, + sharePath: + publicWorkCode && status === 'published' + ? buildPublicWorkStagePath('work-detail', publicWorkCode) + : null, + openActionLabel: status === 'published' ? '查看详情' : '继续创作', + canDelete, + canShare: status === 'published' && Boolean(publicWorkCode), + badges: [ + buildStatusBadge(status), + { id: 'type', label: '拼消消', tone: 'neutral' }, + ], + metrics: + status === 'published' + ? buildPublishedMetrics({ + playCount: item.playCount, + remixCount: 0, + likeCount: 0, + }) + : [], + actions: buildWorkShelfActions(item, adapter), + source: { kind: 'puzzle-clear', item }, + }; +} + function resolveAuthorDisplayName(...sources: Array) { for (const source of sources) { const authorDisplayName = @@ -1119,6 +1190,8 @@ function isPersistedCreationWorkGenerating(item: CreationWorkShelfItem) { return isPersistedPuzzleDraftGenerating(item.source.item); case 'wooden-fish': return item.source.item.generationStatus === 'generating'; + case 'puzzle-clear': + return item.source.item.generationStatus === 'generating'; case 'bark-battle': return isPersistedBarkBattleDraftGenerating(item.source.item); default: diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 8458b506..845de7d4 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -70,6 +70,7 @@ import type { PuzzleAgentSessionSnapshot, SendPuzzleAgentMessageRequest, } from '../../../packages/shared/src/contracts/puzzleAgentSession'; +import { isPuzzleCompileActionReady } from './puzzleDraftGenerationState'; import type { PuzzleCreativeTemplateSelection } from '../../../packages/shared/src/contracts/puzzleCreativeTemplate'; import type { PuzzleRunSnapshot, @@ -130,6 +131,7 @@ import { } from '../../services/authService'; import { createBarkBattleDraft, + deleteBarkBattleWork, listBarkBattleGallery, listBarkBattleWorks, publishBarkBattleWork, @@ -217,6 +219,7 @@ import { buildJumpHopGenerationAnchorEntries, buildMatch3DGenerationAnchorEntries, buildMiniGameDraftGenerationProgress, + buildPuzzleClearGenerationAnchorEntries, buildPuzzleGenerationAnchorEntries, buildSquareHoleGenerationAnchorEntries, buildWoodenFishGenerationAnchorEntries, @@ -238,6 +241,7 @@ import { buildBigFishPublicWorkCode, buildJumpHopPublicWorkCode, buildMatch3DPublicWorkCode, + buildPuzzleClearPublicWorkCode, buildPuzzlePublicWorkCode, buildSquareHolePublicWorkCode, buildVisualNovelPublicWorkCode, @@ -247,6 +251,7 @@ import { isSameBigFishPublicWorkCode, isSameJumpHopPublicWorkCode, isSameMatch3DPublicWorkCode, + isSamePuzzleClearPublicWorkCode, isSamePuzzlePublicWorkCode, isSameSquareHolePublicWorkCode, isSameVisualNovelPublicWorkCode, @@ -258,6 +263,24 @@ import { getPuzzleAgentSession, streamPuzzleAgentMessage, } from '../../services/puzzle-agent'; +import { + puzzleClearClient, + type PuzzleClearGalleryCardResponse, + type PuzzleClearRunResponse, + type PuzzleClearSessionResponse, + type PuzzleClearSessionSnapshotResponse, + type PuzzleClearWorkProfileResponse, + type PuzzleClearWorkspaceCreateRequest, + type PuzzleClearWorkSummaryResponse, +} from '../../services/puzzle-clear/puzzleClearClient'; +import { + advancePuzzleClearLocalLevel, + createPuzzleClearLocalRuntimeSnapshot, + isPuzzleClearLocalRuntimeSnapshot, + markPuzzleClearLocalTimeUp, + retryPuzzleClearLocalLevel, + swapPuzzleClearLocalCards, +} from '../../services/puzzle-clear/puzzleClearLocalRuntime'; import { getPuzzleGalleryDetail, likePuzzleGalleryWork, @@ -369,11 +392,13 @@ import { resolvePuzzleWorkCoverImageSrc, } from '../custom-world-home/creationWorkShelf'; import { + buildPlatformPublicGalleryCardKey, isBarkBattleGalleryEntry, isBigFishGalleryEntry, isEdutainmentGalleryEntry, isJumpHopGalleryEntry, isMatch3DGalleryEntry, + isPuzzleClearGalleryEntry, isPuzzleGalleryEntry, isSquareHoleGalleryEntry, isVisualNovelGalleryEntry, @@ -383,6 +408,7 @@ import { mapBigFishWorkToPlatformGalleryCard, mapJumpHopWorkToPlatformGalleryCard, mapMatch3DWorkToPlatformGalleryCard, + mapPuzzleClearWorkToPlatformGalleryCard, mapPuzzleWorkToPlatformGalleryCard, mapSquareHoleWorkToPlatformGalleryCard, mapVisualNovelWorkToPlatformGalleryCard, @@ -510,6 +536,7 @@ export function resolveMiniGameGenerationProgressTickState( 'match3d-generating': 'match3d', 'baby-object-match-generating': 'baby-object-match', 'jump-hop-generating': 'jump-hop', + 'puzzle-clear-generating': 'puzzle-clear', 'wooden-fish-generating': 'wooden-fish', }; const kind = stageKindMap[selectionStage]; @@ -546,6 +573,7 @@ type RecommendRuntimeKind = | 'jump-hop' | 'match3d' | 'puzzle' + | 'puzzle-clear' | 'square-hole' | 'wooden-fish' | 'visual-novel' @@ -564,6 +592,10 @@ type BabyObjectMatchRuntimeReturnStage = | 'work-detail' | 'platform'; type JumpHopRuntimeReturnStage = 'jump-hop-result' | 'work-detail' | 'platform'; +type PuzzleClearRuntimeReturnStage = + | 'puzzle-clear-result' + | 'work-detail' + | 'platform'; type WoodenFishRuntimeReturnStage = | 'wooden-fish-result' | 'work-detail' @@ -579,6 +611,7 @@ type RecommendRuntimeState = { jumpHopRun: JumpHopRunResponse['run'] | null; match3dRun: Match3DRunSnapshot | null; puzzleRun: PuzzleRunSnapshot | null; + puzzleClearRun: PuzzleClearRunResponse['run'] | null; squareHoleRun: SquareHoleRunSnapshot | null; visualNovelRun: VisualNovelRunSnapshot | null; woodenFishRun: WoodenFishRunResponse['run'] | null; @@ -661,26 +694,7 @@ function getPlatformPublicGalleryEntryTime(entry: PlatformPublicGalleryCard) { } function getPlatformPublicGalleryEntryKey(entry: PlatformPublicGalleryCard) { - const kind = isBigFishGalleryEntry(entry) - ? 'big-fish' - : isPuzzleGalleryEntry(entry) - ? 'puzzle' - : isJumpHopGalleryEntry(entry) - ? 'jump-hop' - : isWoodenFishGalleryEntry(entry) - ? 'wooden-fish' - : isMatch3DGalleryEntry(entry) - ? 'match3d' - : isSquareHoleGalleryEntry(entry) - ? 'square-hole' - : isVisualNovelGalleryEntry(entry) - ? 'visual-novel' - : isBarkBattleGalleryEntry(entry) - ? 'bark-battle' - : isEdutainmentGalleryEntry(entry) - ? `edutainment:${entry.templateId}` - : 'rpg'; - return `${kind}:${entry.ownerUserId}:${entry.profileId}`; + return buildPlatformPublicGalleryCardKey(entry); } function getPlatformRecommendRuntimeKind( @@ -694,6 +708,10 @@ function getPlatformRecommendRuntimeKind( return 'puzzle'; } + if (isPuzzleClearGalleryEntry(entry)) { + return 'puzzle-clear'; + } + if (isJumpHopGalleryEntry(entry)) { return 'jump-hop'; } @@ -752,6 +770,9 @@ function isRecommendRuntimeReadyForEntry( state.puzzleRun?.currentLevel?.profileId === entry.profileId ); } + if (expectedKind === 'puzzle-clear') { + return Boolean(state.puzzleClearRun); + } if (expectedKind === 'square-hole') { return Boolean(state.squareHoleRun); } @@ -1998,6 +2019,69 @@ function buildJumpHopCreationUrlState(params: { }; } +function buildPuzzleClearCreationUrlState(params: { + session?: PuzzleClearSessionSnapshotResponse | null; + work?: PuzzleClearWorkProfileResponse | null; +}): CreationUrlState { + const sessionId = normalizeCreationUrlValue(params.session?.sessionId); + const profileId = normalizeCreationUrlValue( + params.work?.summary.profileId ?? params.session?.draft?.profileId, + ); + return { + sessionId, + profileId, + workId: normalizeCreationUrlValue(params.work?.summary.workId ?? profileId), + }; +} + +function buildPuzzleClearSessionFromWorkDetail( + work: PuzzleClearWorkProfileResponse, + fallbackItem?: PuzzleClearWorkSummaryResponse | null, +): PuzzleClearSessionSnapshotResponse { + const sessionId = + normalizeCreationUrlValue(work.summary.sourceSessionId) ?? + normalizeCreationUrlValue(fallbackItem?.sourceSessionId) ?? + work.summary.profileId; + return { + sessionId, + ownerUserId: work.summary.ownerUserId, + status: work.summary.generationStatus, + draft: work.draft, + createdAt: work.summary.updatedAt, + updatedAt: work.summary.updatedAt, + }; +} + +function buildPuzzleClearPendingSession( + item: PuzzleClearWorkSummaryResponse, +): PuzzleClearSessionSnapshotResponse { + const sessionId = + normalizeCreationUrlValue(item.sourceSessionId) ?? item.profileId; + return { + sessionId, + ownerUserId: item.ownerUserId, + status: item.generationStatus, + draft: { + templateId: 'puzzle-clear', + templateName: '拼消消', + profileId: item.profileId, + workTitle: item.workTitle, + workDescription: item.workDescription, + themePrompt: item.themePrompt, + boardBackgroundPrompt: item.themePrompt, + generateBoardBackground: true, + boardBackgroundAsset: null, + cardBackImageSrc: null, + atlasAsset: null, + patternGroups: [], + cardAssets: [], + generationStatus: item.generationStatus, + }, + createdAt: item.updatedAt, + updatedAt: item.updatedAt, + }; +} + function buildJumpHopPendingSession( item: JumpHopWorkSummaryResponse, ): JumpHopSessionSnapshotResponse { @@ -2295,6 +2379,8 @@ function buildDraftCompletionDialogSource( return formatPlatformTaskCompletionSource('方洞挑战草稿', sourceId); case 'jump-hop': return formatPlatformTaskCompletionSource('跳一跳草稿', sourceId); + case 'puzzle-clear': + return formatPlatformTaskCompletionSource('拼消消草稿', sourceId); case 'wooden-fish': return formatPlatformTaskCompletionSource('敲木鱼草稿', sourceId); case 'puzzle': @@ -2644,6 +2730,13 @@ function getGenerationNoticeShelfKeys(item: CreationWorkShelfItem): string[] { item.source.item.profileId, item.source.item.sourceSessionId, ]); + case 'puzzle-clear': + return collectDraftNoticeKeys('puzzle-clear', [ + item.id, + item.source.item.workId, + item.source.item.profileId, + item.source.item.sourceSessionId, + ]); case 'puzzle': return collectDraftNoticeKeys('puzzle', [ item.id, @@ -2880,6 +2973,48 @@ function buildPendingWoodenFishWorks( }); } +function buildPendingPuzzleClearWorks( + pending: Record | undefined, + existingItems: readonly PuzzleClearWorkSummaryResponse[], +): PuzzleClearWorkSummaryResponse[] { + if (!pending) { + return []; + } + + return Object.entries(pending) + .filter(([sessionId]) => + existingItems.every((item) => item.sourceSessionId !== sessionId), + ) + .map(([sessionId, state]) => { + const generationStatus = + state.status === 'failed' + ? 'failed' + : state.status === 'generating' + ? 'generating' + : 'ready'; + return { + runtimeKind: 'puzzle-clear', + workId: `puzzle-clear-work-${sessionId}`, + profileId: sessionId, + ownerUserId: '', + sourceSessionId: sessionId, + workTitle: '拼消消草稿', + workDescription: + state.status === 'failed' + ? '拼消消草稿生成失败,可重新打开处理。' + : '正在生成拼消消草稿。', + themePrompt: '', + coverImageSrc: null, + publicationStatus: 'draft', + playCount: 0, + updatedAt: state.updatedAt, + publishedAt: null, + publishReady: false, + generationStatus, + }; + }); +} + function buildPendingMatch3DWorks( pending: Record | undefined, existingItems: readonly Match3DWorkSummary[], @@ -3343,6 +3478,27 @@ const JumpHopRuntimeShell = lazy(async () => { }; }); +const PuzzleClearWorkspace = lazy(async () => { + const module = await import('../puzzle-clear-creation/PuzzleClearWorkspace'); + return { + default: module.PuzzleClearWorkspace, + }; +}); + +const PuzzleClearResultView = lazy(async () => { + const module = await import('../puzzle-clear-result/PuzzleClearResultView'); + return { + default: module.PuzzleClearResultView, + }; +}); + +const PuzzleClearRuntimeShell = lazy(async () => { + const module = await import('../puzzle-clear-runtime/PuzzleClearRuntimeShell'); + return { + default: module.PuzzleClearRuntimeShell, + }; +}); + const WoodenFishResultView = lazy(async () => { const module = await import('../wooden-fish-result/WoodenFishResultView'); return { @@ -3658,6 +3814,25 @@ export function PlatformEntryFlowShellImpl({ useState(null); const [jumpHopError, setJumpHopError] = useState(null); const [isJumpHopBusy, setIsJumpHopBusy] = useState(false); + const [puzzleClearSession, setPuzzleClearSession] = + useState(null); + const [puzzleClearRun, setPuzzleClearRun] = useState< + PuzzleClearRunResponse['run'] | null + >(null); + const [puzzleClearWork, setPuzzleClearWork] = + useState(null); + const [puzzleClearWorks, setPuzzleClearWorks] = useState< + PuzzleClearWorkSummaryResponse[] + >([]); + const [puzzleClearGalleryEntries, setPuzzleClearGalleryEntries] = useState< + PuzzleClearGalleryCardResponse[] + >([]); + const [puzzleClearRuntimeReturnStage, setPuzzleClearRuntimeReturnStage] = + useState('puzzle-clear-result'); + const [puzzleClearGenerationState, setPuzzleClearGenerationState] = + useState(null); + const [puzzleClearError, setPuzzleClearError] = useState(null); + const [isPuzzleClearBusy, setIsPuzzleClearBusy] = useState(false); const [barkBattleWorks, setBarkBattleWorks] = useState< BarkBattleWorkSummary[] >([]); @@ -3888,6 +4063,10 @@ export function PlatformEntryFlowShellImpl({ creationEntryTypes, 'jump-hop', ); + const isPuzzleClearCreationVisible = isPlatformCreationTypeVisible( + creationEntryTypes, + 'puzzle-clear', + ); const isSquareHoleCreationVisible = isPlatformCreationTypeVisible( creationEntryTypes, 'square-hole', @@ -4582,6 +4761,36 @@ export function PlatformEntryFlowShellImpl({ } }, [isJumpHopCreationVisible]); + const refreshPuzzleClearGallery = useCallback(async () => { + try { + const galleryResponse = await puzzleClearClient.listGallery(); + setPuzzleClearGalleryEntries(galleryResponse.items); + return galleryResponse.items; + } catch { + setPuzzleClearGalleryEntries([]); + return []; + } + }, []); + + const refreshPuzzleClearShelf = useCallback(async () => { + if (!isPuzzleClearCreationVisible) { + setPuzzleClearWorks([]); + return []; + } + + try { + const worksResponse = await puzzleClearClient.listWorks(); + setPuzzleClearWorks(worksResponse.items); + return worksResponse.items; + } catch (error) { + setPuzzleClearWorks([]); + setPuzzleClearError( + resolvePuzzleErrorMessage(error, '读取拼消消作品列表失败。'), + ); + return []; + } + }, [isPuzzleClearCreationVisible]); + const refreshWoodenFishGallery = useCallback(async () => { try { const galleryResponse = await woodenFishClient.listGallery(); @@ -5062,6 +5271,9 @@ export function PlatformEntryFlowShellImpl({ const jumpHopPublicEntries = jumpHopGalleryEntries.map( mapJumpHopWorkToPlatformGalleryCard, ); + const puzzleClearPublicEntries = puzzleClearGalleryEntries.map( + mapPuzzleClearWorkToPlatformGalleryCard, + ); const woodenFishPublicEntries = woodenFishGalleryEntries.map( mapWoodenFishWorkToPlatformGalleryCard, ); @@ -5077,6 +5289,7 @@ export function PlatformEntryFlowShellImpl({ ...barkBattlePublicEntries, ...squareHolePublicEntries, ...jumpHopPublicEntries, + ...puzzleClearPublicEntries, ...woodenFishPublicEntries, ...(isVisualNovelCreationOpen ? visualNovelPublicEntries : []), ...babyObjectMatchPublicEntries, @@ -5091,6 +5304,7 @@ export function PlatformEntryFlowShellImpl({ jumpHopGalleryEntries, match3dGalleryEntries, platformBootstrap.publishedGalleryEntries, + puzzleClearGalleryEntries, puzzleGalleryEntries, barkBattleGalleryEntries, barkBattleWorks, @@ -5108,6 +5322,9 @@ export function PlatformEntryFlowShellImpl({ : []), ...match3dGalleryEntries.map(mapMatch3DWorkToPublicWorkDetail), ...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard), + ...puzzleClearGalleryEntries.map( + mapPuzzleClearWorkToPlatformGalleryCard, + ), ...barkBattleGalleryEntries.map( mapBarkBattleWorkToPlatformGalleryCard, ), @@ -5144,6 +5361,7 @@ export function PlatformEntryFlowShellImpl({ jumpHopGalleryEntries, match3dGalleryEntries, platformBootstrap.publishedGalleryEntries, + puzzleClearGalleryEntries, puzzleGalleryEntries, squareHoleGalleryEntries, visualNovelGalleryEntries, @@ -5195,6 +5413,16 @@ export function PlatformEntryFlowShellImpl({ ], [jumpHopWorks, pendingDraftShelfItems], ); + const puzzleClearShelfItems = useMemo( + () => [ + ...buildPendingPuzzleClearWorks( + pendingDraftShelfItems['puzzle-clear'], + puzzleClearWorks, + ), + ...puzzleClearWorks, + ], + [pendingDraftShelfItems, puzzleClearWorks], + ); const woodenFishShelfItems = useMemo( () => [ ...buildPendingWoodenFishWorks( @@ -5330,6 +5558,13 @@ export function PlatformEntryFlowShellImpl({ item.sourceSessionId, ]), ), + ...puzzleClearShelfItems.flatMap((item) => + collectDraftNoticeKeys('puzzle-clear', [ + item.workId, + item.profileId, + item.sourceSessionId, + ]), + ), ...woodenFishShelfItems.flatMap((item) => collectDraftNoticeKeys('wooden-fish', [ item.workId, @@ -5380,6 +5615,7 @@ export function PlatformEntryFlowShellImpl({ barkBattleShelfItems, bigFishShelfItems, jumpHopShelfItems, + puzzleClearShelfItems, woodenFishShelfItems, creationHubItems, isSquareHoleCreationVisible, @@ -5432,6 +5668,7 @@ export function PlatformEntryFlowShellImpl({ match3d: match3dGenerationState, 'baby-object-match': babyObjectMatchGenerationState, 'jump-hop': jumpHopGenerationState, + 'puzzle-clear': puzzleClearGenerationState, 'wooden-fish': woodenFishGenerationState, }, ); @@ -5460,11 +5697,12 @@ export function PlatformEntryFlowShellImpl({ jumpHopGenerationState, match3dGenerationState, puzzleGenerationState, - selectionStage, + puzzleClearGenerationState, squareHoleGenerationState, + woodenFishGenerationState, + selectionStage, visualNovelGenerationPhase, visualNovelGenerationStartedAtMs, - woodenFishGenerationState, ]); const runProtectedAction = useCallback( @@ -6279,7 +6517,7 @@ export function PlatformEntryFlowShellImpl({ sessionController.setCreationTypeError(errorMessage); setPuzzleCreationError(errorMessage); }, - onActionComplete: async ({ payload, response, setSession }) => { + onActionComplete: async ({ payload, response, session, setSession }) => { setPuzzleOperation(response.operation); setSession(response.session); const formPayload = buildPuzzleFormPayloadFromAction(payload); @@ -6303,6 +6541,47 @@ export function PlatformEntryFlowShellImpl({ if (payload.action === 'compile_puzzle_draft') { const openResult = selectionStageRef.current === 'puzzle-generating'; + if (!isPuzzleCompileActionReady(response.session)) { + const nextPayload = + formPayload ?? buildPuzzleFormPayloadFromSession(response.session); + const fallbackGenerationState = createPuzzleDraftGenerationStateFromPayload( + nextPayload, + response.session, + ); + const nextGenerationState = mergePuzzleSessionProgressIntoGenerationState( + puzzleGenerationState ?? fallbackGenerationState, + response.session, + ); + activePuzzleGenerationSessionIdRef.current = response.session.sessionId; + setSelectionStage('puzzle-generating'); + markDraftGenerating('puzzle', [ + response.session.sessionId, + buildPuzzleResultWorkId(response.session.sessionId), + response.session.publishedProfileId, + buildPuzzleResultProfileId(response.session.sessionId), + ]); + markPendingDraftGenerating( + 'puzzle', + response.session.sessionId, + buildPendingPuzzleDraftMetadata(nextPayload), + ); + setPuzzleGenerationState(nextGenerationState); + setPuzzleBackgroundCompileTasks((current) => { + const next = { ...current }; + if (session.sessionId !== response.session.sessionId) { + delete next[session.sessionId]; + } + next[response.session.sessionId] = { + session: response.session, + payload: nextPayload, + generationState: nextGenerationState, + error: null, + }; + return next; + }); + void refreshPuzzleShelf(); + return { openResult: false }; + } setPuzzleGenerationState((current) => current ? resolveFinishedMiniGameDraftGenerationState(current, 'ready', { @@ -6888,6 +7167,20 @@ export function PlatformEntryFlowShellImpl({ ), message: jumpHopError, }, + { + key: 'puzzle-clear', + source: formatPlatformErrorSource( + selectionStage === 'puzzle-clear-runtime' + ? '拼消消游玩' + : selectionStage === 'puzzle-clear-generating' + ? '拼消消草稿生成' + : '拼消消草稿', + puzzleClearRun?.runId ?? + puzzleClearSession?.sessionId ?? + puzzleClearWork?.summary.profileId, + ), + message: puzzleClearError, + }, { key: 'wooden-fish', source: formatPlatformErrorSource( @@ -7003,6 +7296,10 @@ export function PlatformEntryFlowShellImpl({ pendingPlatformTaskFailureDialog, platformBootstrapErrorForDisplay, publicWorkDetailError, + puzzleClearError, + puzzleClearRun?.runId, + puzzleClearSession?.sessionId, + puzzleClearWork?.summary.profileId, puzzleCreationError, puzzleError, puzzleGenerationViewError, @@ -7117,6 +7414,10 @@ export function PlatformEntryFlowShellImpl({ setJumpHopError(null); return; } + if (currentPlatformErrorDialog.key === 'puzzle-clear') { + setPuzzleClearError(null); + return; + } if (currentPlatformErrorDialog.key === 'wooden-fish') { setWoodenFishError(null); return; @@ -7197,6 +7498,22 @@ export function PlatformEntryFlowShellImpl({ return; } + if (hasRecoverableGeneratedPuzzleDraft(latestSession)) { + const payload = + puzzleGenerationViewPayload ?? + buildPuzzleFormPayloadFromSession(latestSession); + const generationState = + puzzleGenerationViewState ?? + createPuzzleDraftGenerationStateFromPayload(payload, latestSession); + await recoverCompletedPuzzleDraftGeneration({ + sessionId: latestSession.sessionId, + payload, + generationState, + setSession: setPuzzleSession, + }); + return; + } + setPuzzleSession(latestSession); setPuzzleBackgroundCompileTasks((current) => { const task = current[activePuzzleGenerationSessionId]; @@ -7240,6 +7557,9 @@ export function PlatformEntryFlowShellImpl({ }; }, [ activePuzzleGenerationSessionId, + puzzleGenerationViewPayload, + puzzleGenerationViewState, + recoverCompletedPuzzleDraftGeneration, shouldPollPuzzleGenerationSession, setPuzzleSession, ]); @@ -7375,6 +7695,18 @@ export function PlatformEntryFlowShellImpl({ setSelectionStage('jump-hop-workspace'); }, [enterCreateTab, markCreationFlowReturnToCreate, setSelectionStage]); + const openPuzzleClearWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); + setPuzzleClearError(null); + setPuzzleClearSession(null); + setPuzzleClearWork(null); + setPuzzleClearRun(null); + setPuzzleClearGenerationState(null); + enterCreateTab(); + setShowCreationTypeModal(false); + setSelectionStage('puzzle-clear-workspace'); + }, [enterCreateTab, markCreationFlowReturnToCreate, setSelectionStage]); + const openWoodenFishWorkspace = useCallback(() => { markCreationFlowReturnToCreate(); setWoodenFishError(null); @@ -8288,6 +8620,15 @@ export function PlatformEntryFlowShellImpl({ setPuzzleShelfError(null); setPuzzleCreationError(null); setPuzzleError(null); + setPuzzleClearSession(null); + setPuzzleClearWork(null); + setPuzzleClearWorks([]); + setPuzzleClearGalleryEntries([]); + setPuzzleClearRun(null); + setPuzzleClearRuntimeReturnStage('puzzle-clear-result'); + setPuzzleClearGenerationState(null); + setPuzzleClearError(null); + setIsPuzzleClearBusy(false); setVisualNovelSession(null); setVisualNovelWork(null); setVisualNovelWorks([]); @@ -8331,6 +8672,7 @@ export function PlatformEntryFlowShellImpl({ selectionStage !== 'match3d-agent-workspace' && selectionStage !== 'square-hole-agent-workspace' && selectionStage !== 'jump-hop-workspace' && + selectionStage !== 'puzzle-clear-workspace' && selectionStage !== 'wooden-fish-workspace' && selectionStage !== 'puzzle-agent-workspace' && selectionStage !== 'bark-battle-workspace' && @@ -8416,6 +8758,13 @@ export function PlatformEntryFlowShellImpl({ return; } + if (type === 'puzzle-clear') { + runProtectedAction(() => { + void openPuzzleClearWorkspace(); + }); + return; + } + if (type === 'wooden-fish') { runProtectedAction(() => { void openWoodenFishWorkspace(); @@ -8457,6 +8806,7 @@ export function PlatformEntryFlowShellImpl({ openBabyObjectMatchWorkspace, openJumpHopWorkspace, openMatch3DWorkspace, + openPuzzleClearWorkspace, prepareCreationLaunch, openPuzzleWorkspace, openSquareHoleAgentWorkspace, @@ -8506,6 +8856,16 @@ export function PlatformEntryFlowShellImpl({ returnToCreationFlowSource(); }, [returnToCreationFlowSource]); + const leavePuzzleClearFlow = useCallback(() => { + setPuzzleClearRun(null); + setPuzzleClearWork(null); + setPuzzleClearRuntimeReturnStage('puzzle-clear-result'); + setPuzzleClearGenerationState(null); + setPuzzleClearSession(null); + setPuzzleClearError(null); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource]); + const leaveWoodenFishFlow = useCallback(() => { setWoodenFishRun(null); setWoodenFishWork(null); @@ -8525,6 +8885,15 @@ export function PlatformEntryFlowShellImpl({ [], ); + const createReadyPuzzleClearGenerationState = useCallback( + (state: MiniGameDraftGenerationState) => + resolveFinishedMiniGameDraftGenerationState(state, 'ready', { + completedAssetCount: 135, + totalAssetCount: 135, + }), + [], + ); + const createReadyWoodenFishGenerationState = useCallback( (state: MiniGameDraftGenerationState) => resolveFinishedMiniGameDraftGenerationState(state, 'ready', { @@ -9479,20 +9848,26 @@ export function PlatformEntryFlowShellImpl({ const executeSquareHoleAction = squareHoleFlow.executeAction; const retryMatch3DDraftGeneration = useCallback(() => { - if (match3dFormDraftPayload && !match3dSession?.draft?.profileId) { - void createMatch3DDraftFromForm(match3dFormDraftPayload); + if (match3dSession?.sessionId) { + const retryPayload = + match3dFormDraftPayload ?? + buildMatch3DFormPayloadFromSession(match3dSession); + void executeMatch3DAction({ + action: 'match3d_compile_draft', + generateClickSound: retryPayload.generateClickSound, + }); return; } - void executeMatch3DAction({ - action: 'match3d_compile_draft', - generateClickSound: match3dFormDraftPayload?.generateClickSound, - }); + if (match3dFormDraftPayload) { + void createMatch3DDraftFromForm(match3dFormDraftPayload); + return; + } }, [ createMatch3DDraftFromForm, executeMatch3DAction, match3dFormDraftPayload, - match3dSession?.draft?.profileId, + match3dSession, ]); const retrySquareHoleAssetGeneration = useCallback(() => { @@ -9915,6 +10290,468 @@ export function PlatformEntryFlowShellImpl({ [jumpHopRun?.runId, jumpHopRuntimeRequestOptions], ); + const compilePuzzleClearSession = useCallback( + async ( + created: PuzzleClearSessionResponse, + payload?: PuzzleClearWorkspaceCreateRequest, + ) => { + const generationState = createMiniGameDraftGenerationState( + 'puzzle-clear', + resolveMiniGameDraftGenerationStartedAtMs( + created.session.updatedAt ?? created.session.createdAt, + ), + ); + setPuzzleClearError(null); + setPuzzleClearSession(created.session); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ session: created.session }), + ); + setPuzzleClearWork(null); + setPuzzleClearRun(null); + setPuzzleClearGenerationState(generationState); + setIsPuzzleClearBusy(true); + markDraftGenerating('puzzle-clear', [created.session.sessionId]); + markPendingDraftGenerating('puzzle-clear', created.session.sessionId); + selectionStageRef.current = 'puzzle-clear-generating'; + setSelectionStage('puzzle-clear-generating'); + + try { + const response = await puzzleClearClient.executeAction( + created.session.sessionId, + { + actionType: 'compile-draft', + workTitle: payload?.workTitle ?? created.session.draft?.workTitle, + workDescription: + payload?.workDescription ?? + created.session.draft?.workDescription, + themePrompt: + payload?.themePrompt ?? created.session.draft?.themePrompt, + boardBackgroundPrompt: + payload?.boardBackgroundPrompt ?? + created.session.draft?.boardBackgroundPrompt, + generateBoardBackground: + payload?.generateBoardBackground ?? + created.session.draft?.generateBoardBackground, + boardBackgroundAsset: + payload?.boardBackgroundAsset ?? + created.session.draft?.boardBackgroundAsset, + }, + ); + setPuzzleClearSession(response.session); + setPuzzleClearWork(response.work ?? null); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ + session: response.session, + work: response.work, + }), + ); + setPuzzleClearGenerationState( + createReadyPuzzleClearGenerationState(generationState), + ); + if (response.work) { + setPuzzleClearWorks((current) => [ + response.work!.summary, + ...current.filter( + (item) => + item.workId !== response.work!.summary.workId && + item.sourceSessionId !== + response.work!.summary.sourceSessionId, + ), + ]); + markPendingDraftReady( + 'puzzle-clear', + created.session.sessionId, + false, + ); + markDraftReady( + 'puzzle-clear', + [ + created.session.sessionId, + response.work.summary.workId, + response.work.summary.profileId, + response.work.summary.sourceSessionId, + ], + false, + ); + void refreshPuzzleClearShelf().catch(() => undefined); + } + setSelectionStage('puzzle-clear-result'); + } catch (error) { + const errorMessage = resolveRpgCreationErrorMessage( + error, + '生成拼消消草稿失败。', + ); + setPuzzleClearError(errorMessage); + setPuzzleClearGenerationState( + resolveFinishedMiniGameDraftGenerationState( + generationState, + 'failed', + { error: errorMessage }, + ), + ); + markPendingDraftFailed('puzzle-clear', created.session.sessionId); + markDraftFailed( + 'puzzle-clear', + [created.session.sessionId, created.session.draft?.profileId], + errorMessage, + ); + setPuzzleClearWorks((current) => + current.map((item) => + item.sourceSessionId === created.session.sessionId + ? { + ...item, + generationStatus: 'failed', + updatedAt: new Date().toISOString(), + } + : item, + ), + ); + void refreshPuzzleClearShelf().catch(() => undefined); + try { + const latest = await puzzleClearClient.getSession( + created.session.sessionId, + ); + markDraftFailed( + 'puzzle-clear', + [latest.session.sessionId, latest.session.draft?.profileId], + errorMessage, + ); + setPuzzleClearSession(latest.session); + setPuzzleClearWork(null); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ session: latest.session }), + ); + } catch { + setPuzzleClearSession(created.session); + setPuzzleClearWork(null); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ session: created.session }), + ); + } + } finally { + setIsPuzzleClearBusy(false); + } + }, + [ + createReadyPuzzleClearGenerationState, + markDraftFailed, + markDraftGenerating, + markDraftReady, + markPendingDraftFailed, + markPendingDraftGenerating, + markPendingDraftReady, + refreshPuzzleClearShelf, + setSelectionStage, + ], + ); + + const retryPuzzleClearDraftGeneration = useCallback(() => { + if (!puzzleClearSession) { + setSelectionStage('puzzle-clear-workspace'); + return; + } + + void compilePuzzleClearSession({ session: puzzleClearSession }); + }, [compilePuzzleClearSession, puzzleClearSession, setSelectionStage]); + + const regeneratePuzzleClearAtlas = useCallback(async () => { + if (!puzzleClearSession?.sessionId) { + setSelectionStage('puzzle-clear-workspace'); + return; + } + + const generationState = createMiniGameDraftGenerationState('puzzle-clear'); + setPuzzleClearError(null); + setPuzzleClearGenerationState(generationState); + setIsPuzzleClearBusy(true); + selectionStageRef.current = 'puzzle-clear-generating'; + setSelectionStage('puzzle-clear-generating'); + try { + const response = await puzzleClearClient.executeAction( + puzzleClearSession.sessionId, + { + actionType: 'regenerate-atlas', + profileId: + puzzleClearWork?.summary.profileId ?? + puzzleClearSession.draft?.profileId, + workTitle: puzzleClearSession.draft?.workTitle, + workDescription: puzzleClearSession.draft?.workDescription, + themePrompt: puzzleClearSession.draft?.themePrompt, + boardBackgroundPrompt: puzzleClearSession.draft?.boardBackgroundPrompt, + generateBoardBackground: + puzzleClearSession.draft?.generateBoardBackground, + boardBackgroundAsset: puzzleClearSession.draft?.boardBackgroundAsset, + }, + ); + setPuzzleClearSession(response.session); + setPuzzleClearWork(response.work ?? puzzleClearWork); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ + session: response.session, + work: response.work ?? puzzleClearWork, + }), + ); + setPuzzleClearGenerationState( + createReadyPuzzleClearGenerationState(generationState), + ); + setSelectionStage('puzzle-clear-result'); + } catch (error) { + const errorMessage = resolveRpgCreationErrorMessage( + error, + '重新生成拼消消图集失败。', + ); + setPuzzleClearError(errorMessage); + setPuzzleClearGenerationState( + resolveFinishedMiniGameDraftGenerationState( + generationState, + 'failed', + { error: errorMessage }, + ), + ); + } finally { + setIsPuzzleClearBusy(false); + } + }, [ + createReadyPuzzleClearGenerationState, + puzzleClearSession, + puzzleClearWork, + setSelectionStage, + ]); + + const publishPuzzleClearDraft = useCallback(async () => { + const profileId = puzzleClearWork?.summary.profileId?.trim(); + if (!profileId) { + setPuzzleClearError('拼消消草稿尚未生成可发布作品。'); + setSelectionStage('puzzle-clear-result'); + return; + } + + setIsPuzzleClearBusy(true); + setPuzzleClearError(null); + try { + const response = await puzzleClearClient.publishWork(profileId); + setPuzzleClearWork(response.item); + setPuzzleClearWorks((current) => [ + response.item.summary, + ...current.filter((item) => item.workId !== response.item.summary.workId), + ]); + void refreshPuzzleClearShelf(); + void refreshPuzzleClearGallery(); + const publicWorkCode = buildPuzzleClearPublicWorkCode( + response.item.summary.profileId, + ); + setSelectedPublicWorkDetail( + mapPuzzleClearWorkToPlatformGalleryCard(response.item), + ); + setPublicWorkDetailError(null); + selectionStageRef.current = 'work-detail'; + setSelectionStage('work-detail'); + pushAppHistoryPath(buildPublicWorkStagePath('work-detail', publicWorkCode)); + openPublishShareModal({ + title: response.item.summary.workTitle || '拼消消', + publicWorkCode, + stage: 'work-detail', + }); + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '发布拼消消作品失败。'), + ); + setSelectionStage('puzzle-clear-result'); + } finally { + setIsPuzzleClearBusy(false); + } + }, [ + openPublishShareModal, + puzzleClearWork?.summary.profileId, + refreshPuzzleClearGallery, + refreshPuzzleClearShelf, + setSelectionStage, + ]); + + const startPuzzleClearTestRunFromProfile = useCallback(async () => { + if (!puzzleClearWork) { + setPuzzleClearError('拼消消草稿尚未生成可试玩作品。'); + setSelectionStage('puzzle-clear-result'); + return; + } + + setPuzzleClearError(null); + setPuzzleClearRuntimeReturnStage('puzzle-clear-result'); + setPuzzleClearRun(createPuzzleClearLocalRuntimeSnapshot(puzzleClearWork)); + setSelectionStage('puzzle-clear-runtime'); + }, [puzzleClearWork, setSelectionStage]); + + const startPuzzleClearRunFromProfile = useCallback( + async ( + profileId: string, + options: { + embedded?: boolean; + returnStage?: 'work-detail' | 'platform'; + } = {}, + ) => { + const normalizedProfileId = profileId.trim(); + if (!normalizedProfileId) { + setPuzzleClearError('当前拼消消作品信息不完整,暂时无法进入玩法。'); + return false; + } + + setIsPuzzleClearBusy(true); + setPuzzleClearError(null); + setPuzzleClearRuntimeReturnStage(options.returnStage ?? 'work-detail'); + try { + const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions( + authUi, + options.embedded, + ); + const [detail, runResponse] = await Promise.all([ + puzzleClearClient + .getRuntimeWorkDetail(normalizedProfileId) + .catch(() => null), + puzzleClearClient.startRun(normalizedProfileId, runtimeGuestOptions), + ]); + if (detail?.item) { + setPuzzleClearWork(detail.item); + } + setPuzzleClearRun(runResponse.run); + if (!options.embedded) { + setSelectionStage('puzzle-clear-runtime'); + pushAppHistoryPath( + buildPublicWorkStagePath( + 'puzzle-clear-runtime', + buildPuzzleClearPublicWorkCode(normalizedProfileId), + ), + ); + } + return true; + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '启动拼消消玩法失败。'), + ); + return false; + } finally { + setIsPuzzleClearBusy(false); + } + }, + [authUi, setSelectionStage], + ); + + const retryPuzzleClearLevelRun = useCallback(async () => { + const runId = puzzleClearRun?.runId; + if (!runId) { + await startPuzzleClearTestRunFromProfile(); + return; + } + if (isPuzzleClearLocalRuntimeSnapshot(puzzleClearRun)) { + if (!puzzleClearWork) { + setPuzzleClearError('拼消消草稿尚未生成可重试作品。'); + setSelectionStage('puzzle-clear-result'); + return; + } + setPuzzleClearError(null); + setPuzzleClearRun(retryPuzzleClearLocalLevel(puzzleClearRun, puzzleClearWork)); + return; + } + + setIsPuzzleClearBusy(true); + setPuzzleClearError(null); + try { + const response = await puzzleClearClient.retryLevel(runId); + setPuzzleClearRun(response.run); + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '重试拼消消关卡失败。'), + ); + } finally { + setIsPuzzleClearBusy(false); + } + }, [ + puzzleClearRun, + puzzleClearWork, + setSelectionStage, + startPuzzleClearTestRunFromProfile, + ]); + + const advancePuzzleClearLevelRun = useCallback(async () => { + const runId = puzzleClearRun?.runId; + if (!runId) { + return; + } + if (isPuzzleClearLocalRuntimeSnapshot(puzzleClearRun)) { + if (!puzzleClearWork) { + setPuzzleClearError('拼消消草稿尚未生成可继续作品。'); + setSelectionStage('puzzle-clear-result'); + return; + } + setPuzzleClearError(null); + setPuzzleClearRun( + advancePuzzleClearLocalLevel(puzzleClearRun, puzzleClearWork), + ); + return; + } + + setIsPuzzleClearBusy(true); + setPuzzleClearError(null); + try { + const response = await puzzleClearClient.advanceNextLevel(runId); + setPuzzleClearRun(response.run); + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '进入拼消消下一关失败。'), + ); + } finally { + setIsPuzzleClearBusy(false); + } + }, [puzzleClearRun, puzzleClearWork, setSelectionStage]); + + const markPuzzleClearLevelTimeUp = useCallback(async () => { + const runId = puzzleClearRun?.runId; + if (!runId) { + return; + } + if (isPuzzleClearLocalRuntimeSnapshot(puzzleClearRun)) { + setPuzzleClearError(null); + setPuzzleClearRun(markPuzzleClearLocalTimeUp(puzzleClearRun)); + return; + } + + try { + const response = await puzzleClearClient.markTimeUp(runId); + setPuzzleClearRun(response.run); + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '同步拼消消倒计时失败。'), + ); + } + }, [puzzleClearRun]); + + const swapPuzzleClearCardsInRun = useCallback( + async (payload: { + fromRow: number; + fromCol: number; + toRow: number; + toCol: number; + }) => { + const runId = puzzleClearRun?.runId; + if (!runId) { + return; + } + if (isPuzzleClearLocalRuntimeSnapshot(puzzleClearRun)) { + setPuzzleClearError(null); + setPuzzleClearRun(swapPuzzleClearLocalCards(puzzleClearRun, payload)); + return; + } + try { + const response = await puzzleClearClient.swapCards(runId, payload); + setPuzzleClearRun(response.run); + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '交换拼消消卡片失败。'), + ); + } + }, + [puzzleClearRun], + ); + const compileWoodenFishSession = useCallback( async ( created: WoodenFishSessionResponse, @@ -9952,8 +10789,8 @@ export function PlatformEntryFlowShellImpl({ payload?.workDescription ?? created.session.draft?.workDescription ?? '', - themeTags: payload?.themeTags ?? - created.session.draft?.themeTags ?? ['敲木鱼'], + themeTags: + payload?.themeTags ?? created.session.draft?.themeTags ?? ['敲木鱼'], coverImageSrc: created.session.draft?.coverImageSrc ?? null, publicationStatus: 'draft', playCount: 0, @@ -10405,15 +11242,25 @@ export function PlatformEntryFlowShellImpl({ ); const retryPuzzleDraftGeneration = useCallback(() => { - if (puzzleFormDraftPayload) { - void createPuzzleDraftFromForm(puzzleFormDraftPayload); + if (puzzleSession?.sessionId) { + const retryPayload = + puzzleFormDraftPayload ?? + buildPuzzleFormPayloadFromSession(puzzleSession); + void executePuzzleAction( + buildPuzzleCompileActionFromFormPayload(retryPayload), + ); return; } - void executePuzzleAction( - buildPuzzleCompileActionFromFormPayload(puzzleFormDraftPayload), - ); - }, [createPuzzleDraftFromForm, executePuzzleAction, puzzleFormDraftPayload]); + if (puzzleFormDraftPayload) { + void createPuzzleDraftFromForm(puzzleFormDraftPayload); + } + }, [ + createPuzzleDraftFromForm, + executePuzzleAction, + puzzleFormDraftPayload, + puzzleSession, + ]); const retryVisualNovelDraftGeneration = useCallback(() => { if (!visualNovelFormDraftPayload) { @@ -12211,6 +13058,154 @@ export function PlatformEntryFlowShellImpl({ ], ); + const handleDeleteJumpHopWork = useCallback( + (work: JumpHopWorkSummaryResponse) => { + if (deletingCreationWorkId) { + return; + } + const noticeKeys = collectDraftNoticeKeys('jump-hop', [ + work.workId, + work.profileId, + work.sourceSessionId, + ]); + + requestDeleteCreationWork({ + id: work.workId, + title: work.workTitle || '未命名跳一跳', + detail: + work.publicationStatus === 'published' + ? '删除后会从你的作品列表和公开广场中移除。' + : '删除后会从你的作品列表中移除。', + run: () => { + setDeletingCreationWorkId(work.workId); + setJumpHopError(null); + + void jumpHopClient + .deleteWork(work.profileId) + .then((response) => { + markDraftNoticeSeen(noticeKeys); + setJumpHopWorks(response.items); + void refreshJumpHopGallery(); + }) + .catch((error) => { + setJumpHopError( + resolvePuzzleErrorMessage(error, '删除跳一跳作品失败。'), + ); + }) + .finally(() => { + setDeletingCreationWorkId(null); + }); + }, + }); + }, + [ + deletingCreationWorkId, + markDraftNoticeSeen, + refreshJumpHopGallery, + requestDeleteCreationWork, + resolvePuzzleErrorMessage, + setJumpHopError, + ], + ); + + const handleDeleteWoodenFishWork = useCallback( + (work: WoodenFishWorkSummaryResponse) => { + if (deletingCreationWorkId) { + return; + } + const noticeKeys = collectDraftNoticeKeys('wooden-fish', [ + work.workId, + work.profileId, + work.sourceSessionId, + ]); + + requestDeleteCreationWork({ + id: work.workId, + title: work.workTitle || '未命名敲木鱼', + detail: + work.publicationStatus === 'published' + ? '删除后会从你的作品列表和公开广场中移除。' + : '删除后会从你的作品列表中移除。', + run: () => { + setDeletingCreationWorkId(work.workId); + setWoodenFishError(null); + + void woodenFishClient + .deleteWork(work.profileId) + .then((response) => { + markDraftNoticeSeen(noticeKeys); + setWoodenFishWorks(response.items); + void refreshWoodenFishGallery(); + }) + .catch((error) => { + setWoodenFishError( + resolvePuzzleErrorMessage(error, '删除敲木鱼作品失败。'), + ); + }) + .finally(() => { + setDeletingCreationWorkId(null); + }); + }, + }); + }, + [ + deletingCreationWorkId, + markDraftNoticeSeen, + refreshWoodenFishGallery, + requestDeleteCreationWork, + resolvePuzzleErrorMessage, + setWoodenFishError, + ], + ); + + const handleDeleteBarkBattleWork = useCallback( + (work: BarkBattleWorkSummary) => { + if (deletingCreationWorkId) { + return; + } + const noticeKeys = collectDraftNoticeKeys('bark-battle', [ + work.workId, + work.draftId, + ]); + + requestDeleteCreationWork({ + id: work.workId, + title: work.title || '未命名汪汪声浪', + detail: + work.status === 'published' + ? '删除后会从你的作品列表和公开广场中移除。' + : '删除后会从你的作品列表中移除。', + run: () => { + setDeletingCreationWorkId(work.workId); + setBarkBattleError(null); + + void deleteBarkBattleWork(work.workId) + .then((response) => { + markDraftNoticeSeen(noticeKeys); + setBarkBattleWorks(mergeBarkBattleWorksByWorkId(response.items)); + void refreshBarkBattleGallery(); + }) + .catch((error) => { + setBarkBattleError( + resolveBarkBattleErrorMessage(error, '删除汪汪声浪作品失败。'), + ); + }) + .finally(() => { + setDeletingCreationWorkId(null); + }); + }, + }); + }, + [ + deletingCreationWorkId, + markDraftNoticeSeen, + refreshBarkBattleGallery, + requestDeleteCreationWork, + resolveBarkBattleErrorMessage, + setBarkBattleError, + ], + ); + const handleDeleteVisualNovelWork = useCallback( (work: VisualNovelWorkSummary) => { if (deletingCreationWorkId) { @@ -12911,6 +13906,95 @@ export function PlatformEntryFlowShellImpl({ ], ); + const openPuzzleClearPublicWorkDetail = useCallback( + async (profileId: string) => { + setIsPublicWorkDetailBusy(true); + setPuzzleClearError(null); + setPublicWorkDetailError(null); + setSelectionStage('work-detail'); + + try { + const detail = await puzzleClearClient.getRuntimeWorkDetail(profileId); + setPuzzleClearWork(detail.item); + openPublicWorkDetail(mapPuzzleClearWorkToPlatformGalleryCard(detail.item)); + } catch (error) { + setPublicWorkDetailError( + resolveRpgCreationErrorMessage(error, '读取拼消消详情失败。'), + ); + } finally { + setIsPublicWorkDetailBusy(false); + } + }, + [openPublicWorkDetail, setSelectionStage], + ); + + const openPuzzleClearDraft = useCallback( + async (item: PuzzleClearWorkSummaryResponse) => { + markDraftNoticeSeen( + collectDraftNoticeKeys('puzzle-clear', [ + item.workId, + item.profileId, + item.sourceSessionId, + ]), + ); + + if (item.publicationStatus === 'published') { + void openPuzzleClearPublicWorkDetail(item.profileId); + return; + } + + setPuzzleClearError(null); + setPublicWorkDetailError(null); + setIsPuzzleClearBusy(true); + if (item.generationStatus === 'generating') { + const pendingSession = buildPuzzleClearPendingSession(item); + setPuzzleClearSession(pendingSession); + setPuzzleClearRun(null); + setPuzzleClearWork(null); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ session: pendingSession }), + ); + enterCreateTab(); + setSelectionStage('puzzle-clear-generating'); + setIsPuzzleClearBusy(false); + return; + } + try { + const detail = await puzzleClearClient.getWorkDetail(item.profileId); + const recoveredSession = buildPuzzleClearSessionFromWorkDetail( + detail.item, + item, + ); + setPuzzleClearSession(recoveredSession); + setPuzzleClearRun(null); + setPuzzleClearWork(detail.item); + setPuzzleClearRuntimeReturnStage('puzzle-clear-result'); + writeCreationUrlState( + buildPuzzleClearCreationUrlState({ + session: recoveredSession, + work: detail.item, + }), + ); + enterCreateTab(); + setSelectionStage('puzzle-clear-result'); + } catch (error) { + setPuzzleClearError( + resolveRpgCreationErrorMessage(error, '读取拼消消草稿失败。'), + ); + enterCreateTab(); + setSelectionStage('puzzle-clear-generating'); + } finally { + setIsPuzzleClearBusy(false); + } + }, + [ + enterCreateTab, + markDraftNoticeSeen, + openPuzzleClearPublicWorkDetail, + setSelectionStage, + ], + ); + const openWoodenFishPublicWorkDetail = useCallback( async (profileId: string) => { setIsPublicWorkDetailBusy(true); @@ -13035,6 +14119,11 @@ export function PlatformEntryFlowShellImpl({ return; } + if (isPuzzleClearGalleryEntry(entry)) { + void openPuzzleClearPublicWorkDetail(entry.profileId); + return; + } + if (isMatch3DGalleryEntry(entry)) { openPublicWorkDetail(entry); return; @@ -13075,6 +14164,7 @@ export function PlatformEntryFlowShellImpl({ [ openPuzzlePublicWorkDetail, openPublicWorkDetail, + openPuzzleClearPublicWorkDetail, openJumpHopPublicWorkDetail, openWoodenFishPublicWorkDetail, openRpgPublicWorkDetail, @@ -14006,7 +15096,10 @@ export function PlatformEntryFlowShellImpl({ return; } - if (path.startsWith('/creation/puzzle')) { + if ( + path.startsWith('/creation/puzzle') && + !path.startsWith('/creation/puzzle-clear') + ) { const matchedWork = (puzzleWorks.length > 0 ? puzzleWorks @@ -14027,6 +15120,40 @@ export function PlatformEntryFlowShellImpl({ return; } + if (path.startsWith('/creation/puzzle-clear')) { + const matchedWork = + ( + puzzleClearWorks.length > 0 + ? puzzleClearWorks + : (await puzzleClearClient + .listWorks() + .catch(() => ({ items: [] }))).items + ).find( + (item) => + item.sourceSessionId === sessionId || + item.profileId === profileId || + item.workId === workId, + ) ?? null; + if (matchedWork) { + await openPuzzleClearDraft(matchedWork); + return; + } + if (sessionId) { + const { session } = await puzzleClearClient.getSession(sessionId); + setPuzzleClearSession(session); + setPuzzleClearWork(null); + enterCreateTab(); + setSelectionStage( + path.includes('/generating') + ? 'puzzle-clear-generating' + : session.draft + ? 'puzzle-clear-result' + : 'puzzle-clear-workspace', + ); + } + return; + } + if (path.startsWith('/creation/visual-novel')) { const matchedWork = (visualNovelWorks.length > 0 @@ -14167,11 +15294,13 @@ export function PlatformEntryFlowShellImpl({ openBigFishDraft, openMatch3DDraft, openPuzzleDraft, + openPuzzleClearDraft, openSquareHoleDraft, openVisualNovelDraft, platformBootstrap.canReadProtectedData, platformBootstrap.isLoadingPlatform, puzzleFlow, + puzzleClearWorks, puzzleWorks, setSelectionStage, squareHoleFlow, @@ -14339,6 +15468,22 @@ export function PlatformEntryFlowShellImpl({ return; } + if (isPuzzleClearGalleryEntry(selectedPublicWorkDetail)) { + setPublicWorkDetailError(null); + void startPuzzleClearRunFromProfile(selectedPublicWorkDetail.profileId, { + returnStage: 'work-detail', + }); + return; + } + + if (isJumpHopGalleryEntry(selectedPublicWorkDetail)) { + setPublicWorkDetailError(null); + void startJumpHopRunFromProfile(selectedPublicWorkDetail.profileId, { + returnStage: 'work-detail', + }); + return; + } + if (isWoodenFishGalleryEntry(selectedPublicWorkDetail)) { setPublicWorkDetailError(null); void startWoodenFishRunFromProfile(selectedPublicWorkDetail.profileId, { @@ -14455,6 +15600,7 @@ export function PlatformEntryFlowShellImpl({ startBarkBattleRunFromWork, startBigFishRunFromWork, startJumpHopRunFromProfile, + startPuzzleClearRunFromProfile, startWoodenFishRunFromProfile, startPuzzleRunFromProfile, startMatch3DRunFromProfile, @@ -14514,6 +15660,11 @@ export function PlatformEntryFlowShellImpl({ { embedded: true }, ); } + } else if (isPuzzleClearGalleryEntry(entry)) { + started = await startPuzzleClearRunFromProfile(entry.profileId, { + embedded: true, + returnStage: 'platform', + }); } else if (isJumpHopGalleryEntry(entry)) { started = await startJumpHopRunFromProfile(entry.profileId, { embedded: true, @@ -14615,10 +15766,12 @@ export function PlatformEntryFlowShellImpl({ setBigFishError, setMatch3DError, setPuzzleError, + setPuzzleClearError, setSquareHoleError, startBarkBattleRunFromWork, startBigFishRunFromWork, startJumpHopRunFromProfile, + startPuzzleClearRunFromProfile, startWoodenFishRunFromProfile, startMatch3DRunFromProfile, startPuzzleRunFromProfile, @@ -14855,6 +16008,32 @@ export function PlatformEntryFlowShellImpl({ ); } + if (activeRecommendRuntimeKind === 'puzzle-clear') { + return ( + { + setActiveRecommendRuntimeKind(null); + }} + onRetryLevel={() => { + void retryPuzzleClearLevelRun(); + }} + onNextLevel={() => { + void advancePuzzleClearLevelRun(); + }} + onTimeUp={() => { + void markPuzzleClearLevelTimeUp(); + }} + onSwapCards={async (payload) => { + await swapPuzzleClearCardsInRun(payload); + }} + /> + ); + } + if (activeRecommendRuntimeKind === 'wooden-fish') { return ( work.profileId === entry.profileId) ?? + { + runtimeKind: 'puzzle-clear', + workId: entry.workId, + profileId: entry.profileId, + ownerUserId: entry.ownerUserId, + sourceSessionId: entry.sourceSessionId ?? null, + workTitle: entry.worldName, + workDescription: entry.summaryText, + themePrompt: entry.themePrompt, + coverImageSrc: entry.coverImageSrc, + publicationStatus: 'published', + playCount: entry.playCount ?? 0, + updatedAt: entry.updatedAt, + publishedAt: entry.publishedAt, + publishReady: true, + generationStatus: 'ready', + }; + if (!matchedWork?.sourceSessionId?.trim()) { + setPublicWorkDetailError( + '这份拼消消作品缺少原草稿会话,暂时无法编辑。', + ); + return; + } + void openPuzzleClearDraft(matchedWork); + return; + } + if (isMatch3DGalleryEntry(entry)) { const work = mapPublicWorkDetailToMatch3DWork(entry); if (!work?.sourceSessionId?.trim()) { @@ -15391,12 +16618,15 @@ export function PlatformEntryFlowShellImpl({ openBigFishDraft, openMatch3DDraft, openPuzzleDraft, + openPuzzleClearDraft, openSquareHoleDraft, openVisualNovelDraft, barkBattleGalleryEntries, barkBattleWorks, openBabyObjectMatchDraft, openBarkBattleDraft, + puzzleClearWork, + puzzleClearWorks, resolveBabyObjectMatchRuntimeDraft, runProtectedAction, selectedDetailEntry, @@ -15440,6 +16670,7 @@ export function PlatformEntryFlowShellImpl({ const shouldSearchBabyObjectFirst = upperKeyword.startsWith('BO'); const shouldSearchJumpHopFirst = upperKeyword.startsWith('JH'); const shouldSearchWoodenFishFirst = upperKeyword.startsWith('WF'); + const shouldSearchPuzzleClearFirst = upperKeyword.startsWith('PC'); const shouldSearchMatch3DFirst = upperKeyword.startsWith('M3'); const shouldSearchPuzzleFirst = upperKeyword.startsWith('PZ'); const shouldSearchSquareHoleFirst = upperKeyword.startsWith('SH'); @@ -15451,6 +16682,7 @@ export function PlatformEntryFlowShellImpl({ !shouldSearchBigFishFirst && !shouldSearchJumpHopFirst && !shouldSearchWoodenFishFirst && + !shouldSearchPuzzleClearFirst && !shouldSearchMatch3DFirst && !shouldSearchPuzzleFirst && !shouldSearchSquareHoleFirst && @@ -15465,6 +16697,7 @@ export function PlatformEntryFlowShellImpl({ !shouldSearchBabyObjectFirst && !shouldSearchJumpHopFirst && !shouldSearchWoodenFishFirst && + !shouldSearchPuzzleClearFirst && !shouldSearchMatch3DFirst && !shouldSearchPuzzleFirst && !shouldSearchSquareHoleFirst && @@ -15580,6 +16813,28 @@ export function PlatformEntryFlowShellImpl({ openPublicWorkDetail(mapWoodenFishWorkToPublicWorkDetail(matchedEntry)); }; + const tryOpenPuzzleClearGalleryEntry = async () => { + const entries = + puzzleClearGalleryEntries.length > 0 + ? puzzleClearGalleryEntries + : await refreshPuzzleClearGallery(); + const matchedEntry = entries.find((entry) => { + const detailEntry = mapPuzzleClearWorkToPlatformGalleryCard(entry); + return ( + canExposePublicWork(detailEntry) && + isSamePuzzleClearPublicWorkCode( + normalizedKeyword, + entry.profileId, + ) + ); + }); + + if (!matchedEntry) { + throw new Error('未找到拼消消作品。'); + } + + await openPuzzleClearPublicWorkDetail(matchedEntry.profileId); + }; const tryOpenMatch3DGalleryEntry = async () => { const entries = match3dGalleryEntries.length > 0 @@ -15718,6 +16973,11 @@ export function PlatformEntryFlowShellImpl({ return; } + if (shouldSearchPuzzleClearFirst) { + await tryOpenPuzzleClearGalleryEntry(); + return; + } + if (shouldSearchBabyObjectFirst) { await tryOpenBabyObjectMatchGalleryEntry(); return; @@ -15806,14 +17066,17 @@ export function PlatformEntryFlowShellImpl({ bigFishGalleryEntries, jumpHopGalleryEntries, match3dGalleryEntries, + puzzleClearGalleryEntries, refreshMatch3DGallery, openPuzzlePublicWorkDetail, + openPuzzleClearPublicWorkDetail, openPublicWorkDetail, platformBootstrap.platformTab, puzzleGalleryEntries, refreshBarkBattleGallery, refreshBigFishGallery, refreshJumpHopGallery, + refreshPuzzleClearGallery, refreshWoodenFishGallery, refreshPuzzleGallery, refreshSquareHoleGallery, @@ -15901,6 +17164,19 @@ export function PlatformEntryFlowShellImpl({ return; } + if ( + worldType === 'puzzle-clear' || + worldType === 'puzzle_clear' || + work.worldKey.startsWith('puzzle-clear:') + ) { + const profileId = + work.profileId ?? work.worldKey.replace(/^puzzle-clear:/u, ''); + if (profileId) { + void openPuzzleClearPublicWorkDetail(profileId); + } + return; + } + if ( worldType === 'wooden-fish' || worldType === 'wooden_fish' || @@ -15995,6 +17271,7 @@ export function PlatformEntryFlowShellImpl({ openPuzzlePublicWorkDetail, openPublicWorkDetail, openJumpHopPublicWorkDetail, + openPuzzleClearPublicWorkDetail, openWoodenFishPublicWorkDetail, openRpgPublicWorkDetail, openSquareHolePublicWorkDetail, @@ -16023,6 +17300,7 @@ export function PlatformEntryFlowShellImpl({ void refreshBigFishGallery(); } void refreshJumpHopGallery(); + void refreshPuzzleClearGallery(); void refreshWoodenFishGallery(); void refreshMatch3DGallery(); void refreshPuzzleGallery(); @@ -16040,6 +17318,7 @@ export function PlatformEntryFlowShellImpl({ isVisualNovelCreationOpen, refreshBigFishGallery, refreshJumpHopGallery, + refreshPuzzleClearGallery, refreshWoodenFishGallery, refreshMatch3DGallery, refreshPuzzleGallery, @@ -16069,6 +17348,7 @@ export function PlatformEntryFlowShellImpl({ platformBootstrap.canReadProtectedData ) { void refreshPuzzleShelf(); + void refreshPuzzleClearShelf(); void refreshMatch3DShelf(); if (isSquareHoleCreationVisible) { void refreshSquareHoleShelf(); @@ -16091,6 +17371,7 @@ export function PlatformEntryFlowShellImpl({ refreshBarkBattleShelf, refreshMatch3DShelf, refreshPuzzleShelf, + refreshPuzzleClearShelf, refreshWoodenFishShelf, refreshSquareHoleShelf, refreshVisualNovelShelf, @@ -16189,7 +17470,8 @@ export function PlatformEntryFlowShellImpl({ puzzleShelfError ?? puzzleError ?? (isVisualNovelCreationOpen ? visualNovelError : null) ?? - babyObjectMatchError ?? + babyObjectMatchError ?? + puzzleClearError ?? barkBattleError) } onRetry={() => { @@ -16222,6 +17504,7 @@ export function PlatformEntryFlowShellImpl({ void refreshBigFishShelf(); } void refreshMatch3DShelf(); + void refreshPuzzleClearShelf(); if (isSquareHoleCreationVisible) { void refreshSquareHoleShelf(); } @@ -16241,6 +17524,7 @@ export function PlatformEntryFlowShellImpl({ isBigFishBusy || isMatch3DBusy || (isSquareHoleCreationVisible && isSquareHoleBusy) || + isPuzzleClearBusy || isWoodenFishBusy || isPuzzleBusy || (isVisualNovelCreationOpen && isVisualNovelBusy) || @@ -16281,6 +17565,9 @@ export function PlatformEntryFlowShellImpl({ rpgLibraryEntries={platformBootstrap.savedCustomWorldEntries} bigFishItems={isBigFishCreationVisible ? bigFishShelfItems : []} jumpHopItems={isJumpHopCreationVisible ? jumpHopShelfItems : []} + puzzleClearItems={ + isPuzzleClearCreationVisible ? puzzleClearShelfItems : [] + } woodenFishItems={woodenFishShelfItems} onOpenBigFishDetail={ isBigFishCreationVisible @@ -16309,14 +17596,33 @@ export function PlatformEntryFlowShellImpl({ } : null } - onDeleteJumpHop={null} + onDeleteJumpHop={ + isJumpHopCreationVisible + ? (item) => { + handleDeleteJumpHopWork(item); + } + : null + } + onOpenPuzzleClearDetail={ + isPuzzleClearCreationVisible + ? (item) => { + runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); + void openPuzzleClearDraft(item); + }); + } + : undefined + } + onDeletePuzzleClear={null} onOpenWoodenFishDetail={(item) => { runProtectedAction(() => { markCreationFlowReturnToDraftShelf(); void openWoodenFishDraft(item); }); }} - onDeleteWoodenFish={null} + onDeleteWoodenFish={(item) => { + handleDeleteWoodenFishWork(item); + }} match3dItems={match3dShelfItems} onOpenMatch3DDetail={(item) => { runProtectedAction(() => { @@ -16380,6 +17686,9 @@ export function PlatformEntryFlowShellImpl({ openBarkBattleDraft(item); }); }} + onDeleteBarkBattle={(item) => { + handleDeleteBarkBattleWork(item); + }} visualNovelItems={visualNovelShelfItems} onOpenVisualNovelDetail={(item) => { runProtectedAction(() => { @@ -16470,6 +17779,11 @@ export function PlatformEntryFlowShellImpl({ isBigFishBusy || (isPuzzleBusy && !(activeRecommendRuntimeKind === 'puzzle' && puzzleRun)) || + (isPuzzleClearBusy && + !( + activeRecommendRuntimeKind === 'puzzle-clear' && + puzzleClearRun + )) || isMatch3DBusy || isSquareHoleBusy || isVisualNovelBusy || @@ -16570,6 +17884,7 @@ export function PlatformEntryFlowShellImpl({ isBusy={ isPublicWorkDetailBusy || isPuzzleBusy || + isPuzzleClearBusy || isBigFishBusy || isMatch3DBusy || isSquareHoleBusy || @@ -16824,7 +18139,6 @@ export function PlatformEntryFlowShellImpl({ backLabel="返回创作中心" settingActionLabel={null} retryLabel="重新生成草稿" - settingTitle="当前玩法信息" settingDescription={null} progressTitle="大鱼吃小鱼草稿生成进度" activeBadgeLabel="草稿生成中" @@ -17230,7 +18544,6 @@ export function PlatformEntryFlowShellImpl({ backLabel="返回创作中心" settingActionLabel={null} retryLabel="重新生成草稿" - settingTitle="当前宝贝识物信息" settingDescription={null} progressTitle="宝贝识物草稿生成进度" activeBadgeLabel="草稿生成中" @@ -17431,7 +18744,6 @@ export function PlatformEntryFlowShellImpl({ backLabel="返回创作中心" settingActionLabel={null} retryLabel="重新生成图片" - settingTitle="当前方洞挑战" settingDescription={null} progressTitle="方洞挑战图片生成进度" activeBadgeLabel="图片生成中" @@ -17722,6 +19034,142 @@ export function PlatformEntryFlowShellImpl({ )} + {selectionStage === 'puzzle-clear-workspace' && ( + + } + > + { + void compilePuzzleClearSession(result, payload); + }} + /> + + + )} + + {selectionStage === 'puzzle-clear-generating' && ( + + } + > + { + setSelectionStage('puzzle-clear-workspace'); + }} + onRetry={retryPuzzleClearDraftGeneration} + onInterrupt={undefined} + backLabel="返回创作中心" + settingActionLabel={null} + retryLabel="重新生成草稿" + settingTitle="当前拼消消信息" + settingDescription={null} + progressTitle="拼消消草稿生成进度" + activeBadgeLabel="素材生成中" + pausedBadgeLabel="素材生成已暂停" + idleBadgeLabel="等待返回工作区" + /> + + + )} + + {selectionStage === 'puzzle-clear-result' && + puzzleClearSession?.draft && ( + + } + > + { + setSelectionStage('puzzle-clear-workspace'); + }} + onStartTestRun={startPuzzleClearTestRunFromProfile} + onPublish={publishPuzzleClearDraft} + onRegenerateAtlas={() => { + void regeneratePuzzleClearAtlas(); + }} + /> + + + )} + + {selectionStage === 'puzzle-clear-runtime' && ( + + } + > + { + setSelectionStage(puzzleClearRuntimeReturnStage); + }} + onRetryLevel={() => { + void retryPuzzleClearLevelRun(); + }} + onNextLevel={() => { + void advancePuzzleClearLevelRun(); + }} + onTimeUp={() => { + void markPuzzleClearLevelTimeUp(); + }} + onSwapCards={async (payload) => { + await swapPuzzleClearCardsInRun(payload); + }} + /> + + + )} + {selectionStage === 'wooden-fish-workspace' && ( { + test('ticks while puzzle clear generation is still running', () => { + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'puzzle-clear-generating', + generationState: createMiniGameDraftGenerationState('puzzle-clear'), + }), + ).toBe(true); + }); + + test('stops ticking after puzzle clear generation is ready or failed', () => { + const runningState = createMiniGameDraftGenerationState('puzzle-clear'); + + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'puzzle-clear-generating', + generationState: { ...runningState, phase: 'ready' }, + }), + ).toBe(false); + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'puzzle-clear-generating', + generationState: { ...runningState, phase: 'failed' }, + }), + ).toBe(false); + }); + + test('ticks for other shared mini game generation stages', () => { + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'jump-hop-generating', + generationState: createMiniGameDraftGenerationState('jump-hop'), + }), + ).toBe(true); + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'wooden-fish-generating', + generationState: createMiniGameDraftGenerationState('wooden-fish'), + }), + ).toBe(true); + }); + + test('ticks visual novel generation from its phase source', () => { + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'visual-novel-generating', + visualNovelGenerationStartedAtMs: 1000, + visualNovelGenerationPhase: 'generating', + }), + ).toBe(true); + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'visual-novel-generating', + visualNovelGenerationStartedAtMs: 1000, + visualNovelGenerationPhase: 'ready', + }), + ).toBe(false); + }); + + test('does not tick when no generating stage is active', () => { + expect( + shouldTickPlatformGenerationProgressClock({ + selectionStage: 'platform', + generationState: createMiniGameDraftGenerationState('puzzle-clear'), + }), + ).toBe(false); + }); +}); diff --git a/src/components/platform-entry/platformGenerationProgressClock.ts b/src/components/platform-entry/platformGenerationProgressClock.ts new file mode 100644 index 00000000..dfc6421c --- /dev/null +++ b/src/components/platform-entry/platformGenerationProgressClock.ts @@ -0,0 +1,36 @@ +import type { MiniGameDraftGenerationState } from '../../services/miniGameDraftGenerationProgress'; +import type { SelectionStage } from './platformEntryTypes'; + +type VisualNovelEntryGenerationPhase = 'generating' | 'ready' | 'failed'; + +type PlatformGenerationProgressClockInput = { + selectionStage: SelectionStage; + generationState?: MiniGameDraftGenerationState | null; + visualNovelGenerationStartedAtMs?: number | null; + visualNovelGenerationPhase?: VisualNovelEntryGenerationPhase; +}; + +export function shouldTickPlatformGenerationProgressClock({ + selectionStage, + generationState, + visualNovelGenerationStartedAtMs, + visualNovelGenerationPhase, +}: PlatformGenerationProgressClockInput) { + if (selectionStage === 'visual-novel-generating') { + return ( + visualNovelGenerationStartedAtMs != null && + visualNovelGenerationPhase !== 'ready' && + visualNovelGenerationPhase !== 'failed' + ); + } + + if (!selectionStage.endsWith('-generating')) { + return false; + } + + return Boolean( + generationState && + generationState.phase !== 'ready' && + generationState.phase !== 'failed', + ); +} diff --git a/src/components/platform-entry/puzzleDraftGenerationState.test.ts b/src/components/platform-entry/puzzleDraftGenerationState.test.ts new file mode 100644 index 00000000..dd4a955c --- /dev/null +++ b/src/components/platform-entry/puzzleDraftGenerationState.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; + +import { isPuzzleCompileActionReady } from './puzzleDraftGenerationState'; +import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession'; + +describe('isPuzzleCompileActionReady', () => { + it('keeps compile action generating until the draft has a cover image', () => { + const session = { + sessionId: 'puzzle-session-1', + draft: { + coverImageSrc: null, + levels: [ + { + generationStatus: 'generating', + coverImageSrc: null, + }, + ], + }, + } as PuzzleAgentSessionSnapshot; + + expect(isPuzzleCompileActionReady(session)).toBe(false); + }); + + it('treats compile action as ready after the selected cover exists', () => { + const session = { + sessionId: 'puzzle-session-1', + draft: { + coverImageSrc: '/generated-puzzle-assets/session/cover.png', + levels: [ + { + generationStatus: 'ready', + coverImageSrc: '/generated-puzzle-assets/session/cover.png', + }, + ], + }, + } as PuzzleAgentSessionSnapshot; + + expect(isPuzzleCompileActionReady(session)).toBe(true); + }); +}); diff --git a/src/components/platform-entry/puzzleDraftGenerationState.ts b/src/components/platform-entry/puzzleDraftGenerationState.ts new file mode 100644 index 00000000..f00ee282 --- /dev/null +++ b/src/components/platform-entry/puzzleDraftGenerationState.ts @@ -0,0 +1,20 @@ +import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession'; + +function hasText(value: string | null | undefined) { + return typeof value === 'string' && value.trim().length > 0; +} + +export function isPuzzleCompileActionReady( + session: PuzzleAgentSessionSnapshot, +) { + const draft = session.draft; + if (!draft) { + return false; + } + if (hasText(draft.coverImageSrc)) { + return true; + } + return ( + draft.levels?.some((level) => hasText(level.coverImageSrc)) === true + ); +} diff --git a/src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx b/src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx new file mode 100644 index 00000000..3425ddac --- /dev/null +++ b/src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx @@ -0,0 +1,222 @@ +/* @vitest-environment jsdom */ + +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import type { ImgHTMLAttributes } from 'react'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { PuzzleClearSessionResponse } from '../../../packages/shared/src/contracts/puzzleClear'; +import { puzzleClearClient } from '../../services/puzzle-clear/puzzleClearClient'; +import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage'; +import { PuzzleClearWorkspace } from './PuzzleClearWorkspace'; + +vi.mock('../ResolvedAssetImage', () => ({ + ResolvedAssetImage: ({ + src, + alt, + className, + refreshKey: _refreshKey, + ...rest + }: { + src?: string | null; + alt?: string; + className?: string; + refreshKey?: unknown; + [key: string]: unknown; + }) => + src ? ( + {alt})} + /> + ) : null, +})); + +vi.mock('../../services/puzzleReferenceImage', () => ({ + readPuzzleReferenceImageAsDataUrl: vi.fn(), +})); + +vi.mock('../../services/puzzle-clear/puzzleClearClient', () => ({ + puzzleClearClient: { + createSession: vi.fn(), + }, +})); + +function createSessionResponse(): PuzzleClearSessionResponse { + return { + session: { + sessionId: 'puzzle-clear-session-1', + ownerUserId: 'user-1', + status: 'draft', + draft: { + templateId: 'puzzle-clear', + templateName: '拼消消', + profileId: null, + workTitle: '星港拼消消', + workDescription: '霓虹星港主题', + themePrompt: '霓虹星港', + boardBackgroundPrompt: '星港中央棋盘底图', + generateBoardBackground: false, + boardBackgroundAsset: null, + cardBackImageSrc: '/creation-type-references/puzzle-clear-card-back.webp', + atlasAsset: null, + patternGroups: [], + cardAssets: [], + generationStatus: 'draft', + }, + createdAt: '2026-05-30T00:00:00.000Z', + updatedAt: '2026-05-30T00:00:00.000Z', + }, + }; +} + +beforeEach(() => { + vi.mocked(puzzleClearClient.createSession).mockReset(); + vi.mocked(readPuzzleReferenceImageAsDataUrl).mockReset(); +}); + +test('工作台提交结构化表单与底图槽位 payload', async () => { + const response = createSessionResponse(); + const onSubmitted = vi.fn(); + vi.mocked(puzzleClearClient.createSession).mockResolvedValue(response); + vi.mocked(readPuzzleReferenceImageAsDataUrl).mockResolvedValue( + 'data:image/png;base64,board-background', + ); + + render( + , + ); + + fireEvent.change(screen.getByLabelText('作品标题'), { + target: { value: ' 星港拼消消 ' }, + }); + fireEvent.change(screen.getByLabelText('简介'), { + target: { value: ' 霓虹星港主题 ' }, + }); + fireEvent.change(screen.getByLabelText('主题词'), { + target: { value: ' 霓虹星港 ' }, + }); + fireEvent.change(screen.getByLabelText('场地底图'), { + target: { value: '星港中央棋盘底图' }, + }); + fireEvent.change(screen.getByLabelText('上传底图'), { + target: { + files: [ + new File(['fake-image'], 'board.png', { + type: 'image/png', + }), + ], + }, + }); + + await waitFor(() => + expect(readPuzzleReferenceImageAsDataUrl).toHaveBeenCalledTimes(1), + ); + + fireEvent.click(screen.getByRole('button', { name: '生成' })); + + await waitFor(() => + expect(puzzleClearClient.createSession).toHaveBeenCalledWith({ + templateId: 'puzzle-clear', + workTitle: '星港拼消消', + workDescription: '霓虹星港主题', + themePrompt: '霓虹星港', + boardBackgroundPrompt: '星港中央棋盘底图', + generateBoardBackground: false, + boardBackgroundAsset: expect.objectContaining({ + imageSrc: 'data:image/png;base64,board-background', + generationProvider: 'local-upload', + prompt: '星港中央棋盘底图', + }), + }), + ); + expect(onSubmitted).toHaveBeenCalledWith( + response, + expect.objectContaining({ + templateId: 'puzzle-clear', + workTitle: '星港拼消消', + themePrompt: '霓虹星港', + }), + ); +}); + +test('工作台不渲染聊天式 Agent 输入', () => { + render( + , + ); + + expect(screen.queryByText(/发送消息|聊天|对话|输入想法/u)).toBeNull(); +}); + +test('关闭 AI 生成底图且未上传底图时不允许提交', async () => { + render( + , + ); + + fireEvent.change(screen.getByLabelText('作品标题'), { + target: { value: '星港拼消消' }, + }); + fireEvent.change(screen.getByLabelText('主题词'), { + target: { value: '霓虹星港' }, + }); + fireEvent.click(screen.getByRole('checkbox', { name: 'AI 生成底图' })); + + expect( + (screen.getByRole('button', { name: '生成' }) as HTMLButtonElement).disabled, + ).toBe(true); + + fireEvent.click(screen.getByRole('button', { name: '生成' })); + + await waitFor(() => + expect(puzzleClearClient.createSession).not.toHaveBeenCalled(), + ); +}); + +test('工作台支持原生表单提交生成', async () => { + const response = createSessionResponse(); + const onSubmitted = vi.fn(); + vi.mocked(puzzleClearClient.createSession).mockResolvedValue(response); + + render( + , + ); + + fireEvent.change(screen.getByLabelText('作品标题'), { + target: { value: '星港拼消消' }, + }); + fireEvent.change(screen.getByLabelText('主题词'), { + target: { value: '霓虹星港' }, + }); + fireEvent.change(screen.getByLabelText('场地底图'), { + target: { value: '星港中央棋盘底图' }, + }); + + const submitButton = screen.getByRole('button', { name: '生成' }); + const form = submitButton.closest('form'); + expect(form).toBeTruthy(); + fireEvent.submit(form!); + + await waitFor(() => + expect(puzzleClearClient.createSession).toHaveBeenCalledTimes(1), + ); + expect(onSubmitted).toHaveBeenCalledWith( + response, + expect.objectContaining({ + templateId: 'puzzle-clear', + workTitle: '星港拼消消', + }), + ); +}); diff --git a/src/components/puzzle-clear-creation/PuzzleClearWorkspace.tsx b/src/components/puzzle-clear-creation/PuzzleClearWorkspace.tsx new file mode 100644 index 00000000..75734d04 --- /dev/null +++ b/src/components/puzzle-clear-creation/PuzzleClearWorkspace.tsx @@ -0,0 +1,326 @@ +import { ArrowLeft, Loader2, Send } from 'lucide-react'; +import { useMemo, useState } from 'react'; + +import type { + PuzzleClearImageAsset, + PuzzleClearSessionResponse, + PuzzleClearWorkspaceCreateRequest, +} from '../../../packages/shared/src/contracts/puzzleClear'; +import { puzzleClearClient } from '../../services/puzzle-clear/puzzleClearClient'; +import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage'; +import { CreativeImageInputPanel } from '../common/CreativeImageInputPanel'; + +type PuzzleClearWorkspaceProps = { + isBusy?: boolean; + error?: string | null; + onBack: () => void; + onSubmitted: ( + result: PuzzleClearSessionResponse, + payload: PuzzleClearWorkspaceCreateRequest, + ) => void; +}; + +type PuzzleClearWorkspaceFormState = { + workTitle: string; + workDescription: string; + themePrompt: string; + boardBackgroundPrompt: string; + boardBackgroundAsset: PuzzleClearImageAsset | null; + boardBackgroundImageSrc: string; + generateBoardBackground: boolean; +}; + +const DEFAULT_FORM_STATE: PuzzleClearWorkspaceFormState = { + workTitle: '', + workDescription: '', + themePrompt: '', + boardBackgroundPrompt: '', + boardBackgroundAsset: null, + boardBackgroundImageSrc: '', + generateBoardBackground: true, +}; + +function buildLocalBoardBackgroundAsset( + imageSrc: string, + prompt: string, +): PuzzleClearImageAsset { + return { + assetId: `local-board-background-${Date.now()}`, + imageSrc, + imageObjectKey: '', + assetObjectId: '', + generationProvider: 'local-upload', + prompt, + width: 0, + height: 0, + }; +} + +export function PuzzleClearWorkspace({ + isBusy = false, + error = null, + onBack, + onSubmitted, +}: PuzzleClearWorkspaceProps) { + const [formState, setFormState] = useState(DEFAULT_FORM_STATE); + const [localError, setLocalError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + const hasBoardBackgroundInput = useMemo( + () => + formState.generateBoardBackground || + Boolean(formState.boardBackgroundAsset || formState.boardBackgroundImageSrc), + [ + formState.boardBackgroundAsset, + formState.boardBackgroundImageSrc, + formState.generateBoardBackground, + ], + ); + + const canSubmit = useMemo( + () => + Boolean( + formState.workTitle.trim() && + formState.themePrompt.trim() && + hasBoardBackgroundInput, + ), + [formState.themePrompt, formState.workTitle, hasBoardBackgroundInput], + ); + + const handleSubmit = async () => { + if (!canSubmit || isSubmitting || isBusy) { + setLocalError('请先补全输入。'); + return; + } + + setIsSubmitting(true); + setLocalError(null); + + try { + const boardBackgroundAsset = + formState.boardBackgroundAsset ?? + (formState.boardBackgroundImageSrc + ? buildLocalBoardBackgroundAsset( + formState.boardBackgroundImageSrc, + formState.boardBackgroundPrompt.trim() || + formState.themePrompt.trim(), + ) + : null); + const payload: PuzzleClearWorkspaceCreateRequest = { + templateId: 'puzzle-clear', + workTitle: formState.workTitle.trim(), + workDescription: formState.workDescription.trim(), + themePrompt: formState.themePrompt.trim(), + boardBackgroundPrompt: formState.boardBackgroundPrompt.trim(), + generateBoardBackground: formState.generateBoardBackground, + boardBackgroundAsset, + }; + const response = await puzzleClearClient.createSession(payload); + onSubmitted(response, payload); + } catch (caughtError) { + setLocalError( + caughtError instanceof Error ? caughtError.message : '创建草稿失败。', + ); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
{ + event.preventDefault(); + void handleSubmit(); + }} + className="platform-remap-surface mx-auto flex h-full min-h-0 w-full max-w-5xl flex-col px-3 pb-3 pt-3 sm:px-4 sm:pt-4" + > +
+ +
+ +
+
+ + +