1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-28 10:57:40 +08:00
parent bb4100fca4
commit a9febe7678
28 changed files with 1342 additions and 89 deletions

View File

@@ -94,19 +94,19 @@
},
{
"templateId": "big_fish",
"displayName": "大鱼吃小鱼共创",
"displayName": "大鱼吃小鱼",
"creationGoal": "收束成可直接编译为竖屏大鱼吃小鱼玩法草稿的成长、生态、节奏方案。",
"anchorQuestions": [
{
"key": "gameplayPromise",
"label": "玩法承诺",
"question": "这版大鱼吃小鱼最核心的吞噬成长爽点是什么?",
"label": "玩法爽点",
"question": "核心的吞噬成长爽点是什么?",
"requiredEffect": "明确玩家为什么要持续吞噬、升级和冒险。"
},
{
"key": "ecologyVisualTheme",
"label": "生态视觉主题",
"question": "鱼群、场景和敌我生态的视觉主题是什么?",
"label": "视觉主题",
"question": "场景和形象的视觉主题是什么?",
"requiredEffect": "提供后续角色图、动作图和背景图的一致视觉方向。"
},
{

View File

@@ -80,12 +80,22 @@ impl SpacetimeClient {
procedure_input: BigFishWorksListInput,
) -> Result<Vec<BigFishWorkSummaryRecord>, SpacetimeClientError> {
self.call_after_connect(move |connection, sender| {
let fallback_owner_user_id = if procedure_input.published_only {
None
} else {
Some(procedure_input.owner_user_id.clone())
};
connection
.procedures()
.list_big_fish_works_then(procedure_input, move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_big_fish_works_procedure_result);
.and_then(|result| {
map_big_fish_works_procedure_result(
result,
fallback_owner_user_id.as_deref(),
)
});
send_once(&sender, mapped);
});
})
@@ -103,12 +113,18 @@ impl SpacetimeClient {
};
self.call_after_connect(move |connection, sender| {
let fallback_owner_user_id = Some(procedure_input.owner_user_id.clone());
connection
.procedures()
.delete_big_fish_work_then(procedure_input, move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_big_fish_works_procedure_result);
.and_then(|result| {
map_big_fish_works_procedure_result(
result,
fallback_owner_user_id.as_deref(),
)
});
send_once(&sender, mapped);
});
})

View File

@@ -1278,6 +1278,7 @@ pub(crate) fn map_big_fish_session_procedure_result(
pub(crate) fn map_big_fish_works_procedure_result(
result: BigFishWorksProcedureResult,
fallback_owner_user_id: Option<&str>,
) -> Result<Vec<BigFishWorkSummaryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
@@ -1292,9 +1293,15 @@ pub(crate) fn map_big_fish_works_procedure_result(
"SpacetimeDB procedure 未返回 big fish works 快照".to_string(),
)
})?;
serde_json::from_str::<Vec<BigFishWorkSummaryRecord>>(&items_json).map_err(|error| {
SpacetimeClientError::Runtime(format!("big fish works items_json 非法: {error}"))
})
let items = serde_json::from_str::<Vec<CompatibleBigFishWorkSummaryRecord>>(&items_json)
.map_err(|error| {
SpacetimeClientError::Runtime(format!("big fish works items_json 非法: {error}"))
})?;
Ok(items
.into_iter()
.map(|item| item.into_record(fallback_owner_user_id))
.collect())
}
pub(crate) fn map_story_session_procedure_result(
@@ -4601,6 +4608,120 @@ pub struct BigFishWorkSummaryRecord {
pub background_ready: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
struct CompatibleBigFishWorkSummaryRecord {
work_id: String,
source_session_id: String,
#[serde(default)]
owner_user_id: Option<String>,
title: String,
subtitle: String,
summary: String,
cover_image_src: Option<String>,
status: String,
updated_at_micros: i64,
publish_ready: bool,
level_count: u32,
level_main_image_ready_count: u32,
level_motion_ready_count: u32,
background_ready: bool,
}
impl CompatibleBigFishWorkSummaryRecord {
fn into_record(self, fallback_owner_user_id: Option<&str>) -> BigFishWorkSummaryRecord {
BigFishWorkSummaryRecord {
work_id: self.work_id,
source_session_id: self.source_session_id,
// 中文注释:兼容旧 works JSON 没有 owner_user_id 的历史数据,避免一次字段升级把整个作品列表打崩。
owner_user_id: self.owner_user_id.unwrap_or_else(|| {
fallback_owner_user_id
.map(str::to_string)
.unwrap_or_default()
}),
title: self.title,
subtitle: self.subtitle,
summary: self.summary,
cover_image_src: self.cover_image_src,
status: self.status,
updated_at_micros: self.updated_at_micros,
publish_ready: self.publish_ready,
level_count: self.level_count,
level_main_image_ready_count: self.level_main_image_ready_count,
level_motion_ready_count: self.level_motion_ready_count,
background_ready: self.background_ready,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn big_fish_works_mapper_backfills_missing_owner_user_id_for_private_lists() {
let result = BigFishWorksProcedureResult {
ok: true,
items_json: Some(
r#"[{
"work_id":"big-fish-work-session-1",
"source_session_id":"session-1",
"title":"深海草稿",
"subtitle":"副标题",
"summary":"摘要",
"cover_image_src":null,
"status":"draft",
"updated_at_micros":123,
"publish_ready":false,
"level_count":8,
"level_main_image_ready_count":0,
"level_motion_ready_count":0,
"background_ready":false
}]"#
.to_string(),
),
error_message: None,
};
let items = map_big_fish_works_procedure_result(result, Some("user-1"))
.expect("旧 works JSON 应能被兼容解析");
assert_eq!(items.len(), 1);
assert_eq!(items[0].owner_user_id, "user-1");
}
#[test]
fn big_fish_works_mapper_keeps_empty_owner_when_gallery_legacy_json_lacks_field() {
let result = BigFishWorksProcedureResult {
ok: true,
items_json: Some(
r#"[{
"work_id":"big-fish-work-session-2",
"source_session_id":"session-2",
"title":"公开作品",
"subtitle":"副标题",
"summary":"摘要",
"cover_image_src":null,
"status":"published",
"updated_at_micros":456,
"publish_ready":true,
"level_count":8,
"level_main_image_ready_count":8,
"level_motion_ready_count":16,
"background_ready":true
}]"#
.to_string(),
),
error_message: None,
};
let items = map_big_fish_works_procedure_result(result, None)
.expect("公开 works 旧 JSON 也不应因缺字段报错");
assert_eq!(items.len(), 1);
assert!(items[0].owner_user_id.is_empty());
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolveNpcBattleInteractionInput {
pub npc_interaction: DomainResolveNpcInteractionInput,