feat: add puzzle clear template runtime

This commit is contained in:
2026-06-03 22:11:46 +08:00
parent 6e74cf5add
commit 1b5e098225
148 changed files with 19588 additions and 241 deletions

View File

@@ -455,19 +455,42 @@ fn export_auth_store_snapshot_from_tables_tx(
.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<UserAccount>,
identities: Vec<AuthIdentity>,
sessions: Vec<RefreshSession>,
) -> Result<PersistentAuthStoreSnapshot, String> {
let valid_user_ids = users
.iter()
.map(|user| user.user_id.clone())
.collect::<std::collections::HashSet<_>>();
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();
for identity in identities {
if !valid_user_ids.contains(&identity.user_id) {
continue;
}
match identity.provider.as_str() {
"phone" => {
let phone_number = identity
.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" => {
@@ -490,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
@@ -499,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,
@@ -519,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,
},
);
}
@@ -554,7 +583,7 @@ fn export_auth_store_snapshot_from_tables_tx(
);
}
let snapshot = PersistentAuthStoreSnapshot {
Ok(PersistentAuthStoreSnapshot {
next_user_id,
users_by_username,
phone_to_user_id,
@@ -562,16 +591,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,
})
}
@@ -710,4 +729,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"));
}
}

View File

@@ -5521,6 +5521,7 @@ mod tests {
deleted_at: None,
created_at: Timestamp::from_micros_since_unix_epoch(1),
updated_at: Timestamp::from_micros_since_unix_epoch(1),
visible: true,
}
}

View File

@@ -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::*;

View File

@@ -22,6 +22,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,
@@ -229,6 +233,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,
@@ -1313,6 +1321,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"
@@ -1322,6 +1331,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 == "match_3_d_work_profile" || table_name == "match3d_work_profile" {

View File

@@ -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<PublicWorkGa
.into_iter()
.map(map_puzzle_gallery_entry),
);
entries.extend(
puzzle_clear_gallery_card_view(ctx)
.into_iter()
.map(map_puzzle_clear_gallery_entry),
);
entries.extend(
custom_world_public_gallery_snapshots(ctx)
.into_iter()
@@ -74,6 +80,11 @@ pub fn public_work_detail_entry(ctx: &AnonymousViewContext) -> Vec<PublicWorkDet
.into_iter()
.map(map_puzzle_detail_entry),
);
entries.extend(
puzzle_clear_gallery_view(ctx)
.into_iter()
.map(map_puzzle_clear_detail_entry),
);
entries.extend(
custom_world_public_profile_snapshots(ctx)
.into_iter()
@@ -273,6 +284,65 @@ fn map_puzzle_detail_entry(row: PuzzleWorkProfile) -> 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(),

File diff suppressed because it is too large Load Diff

View File

@@ -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::<String>)]
pub(crate) board_background_prompt: Option<String>,
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<Timestamp>,
// 中文注释:后台可见性开关,隐藏后不进入公开列表。
#[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,
}

View File

@@ -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<String>,
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<String>,
pub board_background_prompt: String,
pub atlas_asset_json: Option<String>,
pub pattern_groups_json: Option<String>,
pub card_assets_json: Option<String>,
pub generation_status: Option<String>,
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<String>,
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<PuzzleClearAgentSessionSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct PuzzleClearWorkProcedureResult {
pub ok: bool,
pub work: Option<PuzzleClearWorkSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct PuzzleClearWorksProcedureResult {
pub ok: bool,
pub items: Vec<PuzzleClearWorkSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct PuzzleClearRunProcedureResult {
pub ok: bool,
pub run: Option<PuzzleClearRuntimeSnapshot>,
pub error_message: Option<String>,
}
#[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<String>,
pub work_title: String,
pub work_description: String,
pub theme_prompt: String,
pub generate_board_background: bool,
pub board_background_asset: Option<PuzzleClearImageAssetSnapshot>,
#[serde(default)]
pub board_background_prompt: String,
pub card_back_image_src: Option<String>,
pub atlas_asset: Option<PuzzleClearImageAssetSnapshot>,
pub pattern_groups: Vec<PuzzleClearPatternGroupSnapshot>,
pub card_assets: Vec<PuzzleClearCardAssetSnapshot>,
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<PuzzleClearDraftSnapshot>,
pub published_profile_id: Option<String>,
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<PuzzleClearImageAssetSnapshot>,
#[serde(default)]
pub board_background_prompt: String,
pub card_back_image_src: Option<String>,
pub atlas_asset: PuzzleClearImageAssetSnapshot,
pub pattern_groups: Vec<PuzzleClearPatternGroupSnapshot>,
pub card_assets: Vec<PuzzleClearCardAssetSnapshot>,
pub cover_image_src: Option<String>,
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<i64>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearBoardCellSnapshot {
pub row: u32,
pub col: u32,
pub card: Option<PuzzleClearCardAssetSnapshot>,
pub locked_group_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearBoardSnapshot {
pub rows: u32,
pub cols: u32,
pub cells: Vec<PuzzleClearBoardCellSnapshot>,
}
#[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<Vec<PuzzleClearCardAssetSnapshot>>,
pub started_at_ms: u64,
pub finished_at_ms: Option<u64>,
}

View File

@@ -1412,6 +1412,7 @@ mod tests {
height: 1536,
})),
back_button_asset_json: None,
visible: true,
}
}
}