Merge remote-tracking branch 'origin/master' into codex/ddd

# Conflicts:
#	docs/technical/README.md
#	docs/technical/RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md
#	docs/technical/SPACETIMEDB_TABLE_CATALOG.md
#	scripts/generate-spacetime-bindings.mjs
#	server-rs/crates/api-server/src/app.rs
#	server-rs/crates/api-server/src/assets.rs
#	server-rs/crates/api-server/src/big_fish.rs
#	server-rs/crates/api-server/src/custom_world_ai.rs
#	server-rs/crates/api-server/src/llm.rs
#	server-rs/crates/api-server/src/main.rs
#	server-rs/crates/api-server/src/puzzle.rs
#	server-rs/crates/api-server/src/runtime_profile.rs
#	server-rs/crates/api-server/src/runtime_story/compat/ai.rs
#	server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs
#	server-rs/crates/api-server/src/runtime_story/compat/presentation.rs
#	server-rs/crates/api-server/src/runtime_story/compat/tests.rs
#	server-rs/crates/api-server/src/state.rs
#	server-rs/crates/module-auth/src/lib.rs
#	server-rs/crates/module-big-fish/src/lib.rs
#	server-rs/crates/module-custom-world/src/lib.rs
#	server-rs/crates/module-puzzle/src/lib.rs
#	server-rs/crates/module-runtime/src/lib.rs
#	server-rs/crates/spacetime-client/src/big_fish.rs
#	server-rs/crates/spacetime-client/src/lib.rs
#	server-rs/crates/spacetime-client/src/mapper.rs
#	server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/big_fish_runtime_run_type.rs
#	server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/mod.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs
#	server-rs/crates/spacetime-module/src/auth/procedures.rs
#	server-rs/crates/spacetime-module/src/custom_world/mod.rs
#	server-rs/crates/spacetime-module/src/lib.rs
#	server-rs/crates/spacetime-module/src/migration.rs
#	server-rs/crates/spacetime-module/src/puzzle.rs
#	server-rs/crates/spacetime-module/src/runtime/profile.rs
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
#	src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
#	src/services/aiService.ts
#	src/services/puzzle-runtime/puzzleRuntimeClient.ts
This commit is contained in:
kdletters
2026-05-02 03:35:59 +08:00
513 changed files with 52813 additions and 6013 deletions

View File

@@ -84,6 +84,34 @@ pub struct CustomWorldGalleryDetailByCodeInput {
pub public_work_code: String,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CustomWorldProfileRemixInput {
pub source_owner_user_id: String,
pub source_profile_id: String,
pub target_owner_user_id: String,
pub target_profile_id: String,
pub author_display_name: String,
pub remixed_at_micros: i64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CustomWorldProfilePlayRecordInput {
pub owner_user_id: String,
pub profile_id: String,
pub played_at_micros: i64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CustomWorldProfileLikeRecordInput {
pub owner_user_id: String,
pub profile_id: String,
pub user_id: String,
pub liked_at_micros: i64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CustomWorldAgentSessionCreateInput {

View File

@@ -155,6 +155,9 @@ pub struct CustomWorldProfileSnapshot {
pub profile_payload_json: String,
pub playable_npc_count: u32,
pub landmark_count: u32,
pub play_count: u32,
pub remix_count: u32,
pub like_count: u32,
pub author_display_name: String,
pub published_at_micros: Option<i64>,
pub deleted_at_micros: Option<i64>,
@@ -177,6 +180,10 @@ pub struct CustomWorldGalleryEntrySnapshot {
pub theme_mode: CustomWorldThemeMode,
pub playable_npc_count: u32,
pub landmark_count: u32,
pub play_count: u32,
pub remix_count: u32,
pub like_count: u32,
pub recent_play_count_7d: u32,
pub published_at_micros: i64,
pub updated_at_micros: i64,
}

View File

@@ -9,329 +9,3 @@ pub use commands::*;
pub use domain::*;
pub use errors::*;
pub use events::*;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
#[test]
fn profile_validation_rejects_blank_owner() {
let error = validate_custom_world_profile_fields(
"cwprof_001",
" ",
"裂潮边城",
"{\"id\":\"cwprof_001\"}",
)
.expect_err("blank owner should fail");
assert_eq!(error, CustomWorldFieldError::MissingOwnerUserId);
}
#[test]
fn agent_session_validation_rejects_progress_over_hundred() {
let error = validate_custom_world_agent_session_fields(
"custom-world-agent-session-001",
"user_001",
"{}",
"{}",
"[]",
"{}",
101,
)
.expect_err("progress greater than 100 should fail");
assert_eq!(error, CustomWorldFieldError::InvalidProgressPercent);
}
#[test]
fn enum_string_values_match_current_contract() {
assert_eq!(
RpgAgentOperationType::PublishWorld.as_str(),
"publish_world"
);
assert_eq!(RpgAgentStage::ReadyToPublish.as_str(), "ready_to_publish");
assert_eq!(RpgAgentMessageRole::Assistant.as_str(), "assistant");
assert_eq!(RpgAgentMessageKind::ActionResult.as_str(), "action_result");
assert_eq!(
RpgAgentDraftCardKind::SceneChapter.as_str(),
"scene_chapter"
);
assert_eq!(
CustomWorldRoleAssetStatus::VisualReady.as_str(),
"visual_ready"
);
assert_eq!(CustomWorldThemeMode::Rift.as_str(), "rift");
}
#[test]
fn agent_session_create_input_validates_required_json_shapes() {
let input = CustomWorldAgentSessionCreateInput {
session_id: "custom-world-agent-session-001".to_string(),
owner_user_id: "user_001".to_string(),
seed_text: "".to_string(),
welcome_message_id: "message-001".to_string(),
welcome_message_text: "你好!我是你的世界设定助手。".to_string(),
anchor_content_json: empty_agent_anchor_content_json(),
creator_intent_json: Some(empty_json_object()),
creator_intent_readiness_json: empty_agent_creator_intent_readiness_json(),
anchor_pack_json: Some(empty_json_object()),
lock_state_json: Some(empty_json_object()),
draft_profile_json: Some(empty_json_object()),
pending_clarifications_json: empty_json_array(),
suggested_actions_json: empty_json_array(),
recommended_replies_json: empty_json_array(),
quality_findings_json: empty_json_array(),
asset_coverage_json: empty_agent_asset_coverage_json(),
checkpoints_json: empty_json_array(),
created_at_micros: 1,
};
validate_custom_world_agent_session_create_input(&input)
.expect("valid skeleton input should pass");
}
#[test]
fn profile_upsert_input_requires_author_display_name() {
let error = validate_custom_world_profile_upsert_input(&CustomWorldProfileUpsertInput {
profile_id: "cwprof_001".to_string(),
owner_user_id: "user_001".to_string(),
public_work_code: None,
author_public_user_code: None,
source_agent_session_id: None,
world_name: "裂潮边城".to_string(),
subtitle: "港口余烬".to_string(),
summary_text: "一座被裂潮与旧械共同撕扯的沿海城邦。".to_string(),
theme_mode: CustomWorldThemeMode::Tide,
cover_image_src: None,
profile_payload_json: "{\"id\":\"cwprof_001\"}".to_string(),
playable_npc_count: 3,
landmark_count: 2,
author_display_name: " ".to_string(),
updated_at_micros: 1,
})
.expect_err("blank author display name should fail");
assert_eq!(error, CustomWorldFieldError::MissingAuthorDisplayName);
}
#[test]
fn canonicalize_profile_before_save_rebuilds_setting_text_from_creator_intent() {
let mut profile = serde_json::json!({
"id": "cwprof_001",
"settingText": "前端旧草稿文案",
"creatorIntent": {
"rawSettingText": "早期输入",
"worldHook": "海图会在午夜改写群岛航路",
"themeKeywords": ["海雾", "旧灯塔"],
"toneDirectives": ["克制", "悬疑"],
"playerPremise": "玩家是失忆领航员",
"openingSituation": "正在禁航区醒来",
"coreConflicts": ["议会隐瞒沉船真相"],
"keyCharacters": [{
"name": "顾潮音",
"role": "守灯人",
"relationToPlayer": "旧识",
"hiddenHook": "掌握伪造海图"
}],
"iconicElements": ["会说谎的罗盘"]
}
});
assert!(canonicalize_custom_world_profile_before_save(&mut profile));
assert_eq!(
profile.get("settingText").and_then(Value::as_str),
Some(
"世界一句话:海图会在午夜改写群岛航路\n玩家开局:玩家是失忆领航员;正在禁航区醒来\n主题气质:海雾、旧灯塔 / 克制、悬疑\n核心冲突:议会隐瞒沉船真相\n关键关系:顾潮音 · 守灯人 · 与玩家 旧识 · 暗线 掌握伪造海图\n标志元素:会说谎的罗盘"
)
);
}
#[test]
fn canonicalize_profile_before_save_keeps_profile_without_creator_intent() {
let mut profile = serde_json::json!({
"id": "cwprof_001",
"settingText": "用户手写设定"
});
assert!(!canonicalize_custom_world_profile_before_save(&mut profile));
assert_eq!(
profile.get("settingText").and_then(Value::as_str),
Some("用户手写设定")
);
}
#[test]
fn profile_list_input_requires_owner_user_id() {
let error = validate_custom_world_profile_list_input(&CustomWorldProfileListInput {
owner_user_id: " ".to_string(),
})
.expect_err("blank owner user id should fail");
assert_eq!(error, CustomWorldFieldError::MissingOwnerUserId);
}
#[test]
fn profile_delete_input_requires_profile_and_owner() {
let error = validate_custom_world_profile_delete_input(&CustomWorldProfileDeleteInput {
profile_id: " ".to_string(),
owner_user_id: "user_001".to_string(),
deleted_at_micros: 1,
})
.expect_err("blank profile id should fail");
assert_eq!(error, CustomWorldFieldError::MissingProfileId);
}
#[test]
fn agent_message_finalize_requires_valid_json_payloads() {
let error = validate_custom_world_agent_message_finalize_input(
&CustomWorldAgentMessageFinalizeInput {
session_id: "session_001".to_string(),
owner_user_id: "user_001".to_string(),
operation_id: "operation_001".to_string(),
assistant_message_id: Some("message_001".to_string()),
assistant_reply_text: Some("已生成回复".to_string()),
phase_label: "消息已处理".to_string(),
phase_detail: "这一轮已完成推理并写回".to_string(),
operation_status: RpgAgentOperationStatus::Completed,
operation_progress: 100,
stage: RpgAgentStage::FoundationReview,
progress_percent: 100,
focus_card_id: None,
anchor_content_json: "[]".to_string(),
creator_intent_json: Some("{}".to_string()),
creator_intent_readiness_json: "{}".to_string(),
anchor_pack_json: Some("{}".to_string()),
draft_profile_json: Some("{}".to_string()),
pending_clarifications_json: "[]".to_string(),
suggested_actions_json: "[]".to_string(),
recommended_replies_json: "[]".to_string(),
quality_findings_json: "[]".to_string(),
asset_coverage_json: "{}".to_string(),
error_message: None,
updated_at_micros: 1,
},
)
.expect_err("invalid anchor content should fail");
assert_eq!(error, CustomWorldFieldError::InvalidJsonPayload);
}
#[test]
fn agent_message_finalize_allows_missing_assistant_reply_when_failed() {
validate_custom_world_agent_message_finalize_input(&CustomWorldAgentMessageFinalizeInput {
session_id: "session_001".to_string(),
owner_user_id: "user_001".to_string(),
operation_id: "operation_001".to_string(),
assistant_message_id: None,
assistant_reply_text: None,
phase_label: "消息处理失败".to_string(),
phase_detail: "当前模型不可用,请稍后重试。".to_string(),
operation_status: RpgAgentOperationStatus::Failed,
operation_progress: 100,
stage: RpgAgentStage::Clarifying,
progress_percent: 20,
focus_card_id: None,
anchor_content_json: "{}".to_string(),
creator_intent_json: Some("{}".to_string()),
creator_intent_readiness_json: "{}".to_string(),
anchor_pack_json: Some("{}".to_string()),
draft_profile_json: Some("{}".to_string()),
pending_clarifications_json: "[]".to_string(),
suggested_actions_json: "[]".to_string(),
recommended_replies_json: "[]".to_string(),
quality_findings_json: "[]".to_string(),
asset_coverage_json: "{}".to_string(),
error_message: Some("当前模型不可用,请稍后重试。".to_string()),
updated_at_micros: 1,
})
.expect("failed finalize should allow empty assistant message");
}
#[test]
fn published_profile_compile_merges_legacy_theme_and_latest_assets() {
let snapshot = build_custom_world_published_profile_compile_snapshot(
CustomWorldPublishedProfileCompileInput {
session_id: "session_001".to_string(),
profile_id: "agent-draft-session_001".to_string(),
owner_user_id: "user_001".to_string(),
draft_profile_json: r#"{
"name":"",
"subtitle":"",
"summary":"稿",
"tone":"湿",
"playerGoal":"",
"playableNpcs":[{"id":"playable-1","name":"","imageSrc":"/generated/playable-1.png"}],
"storyNpcs":[{"id":"story-1","name":""}],
"landmarks":[{"id":"landmark-1","name":"","imageSrc":"/generated/landmark-1.png"}],
"camp":{"id":"camp-1","name":"","imageSrc":"/generated/camp.png"},
"sceneChapters":[{"id":"scene-chapter-1","sceneId":"landmark-1","title":""}]
}"#.to_string(),
legacy_result_profile_json: Some(
r#"{
"id":"legacy_profile",
"themeMode":"tide",
"themePack":{"id":"theme-pack:tide"},
"storyGraph":{"visibleThreads":[{"id":"thread-1"}]}
}"#
.to_string(),
),
setting_text: "被海雾吞没的旧航路群岛".to_string(),
author_display_name: "测试玩家".to_string(),
updated_at_micros: 42,
},
)
.expect("compile should succeed");
assert_eq!(snapshot.world_name, "潮雾列岛");
assert_eq!(snapshot.theme_mode, CustomWorldThemeMode::Tide);
assert_eq!(
snapshot.cover_image_src.as_deref(),
Some("/generated/camp.png")
);
assert_eq!(snapshot.playable_npc_count, 2);
assert_eq!(snapshot.landmark_count, 1);
assert!(
snapshot
.compiled_profile_payload_json
.contains("\"sceneChapterBlueprints\"")
);
assert!(
snapshot
.compiled_profile_payload_json
.contains("\"themePack\"")
);
}
#[test]
fn published_profile_compile_defaults_theme_to_mythic_without_legacy_theme() {
let snapshot = build_custom_world_published_profile_compile_snapshot(
CustomWorldPublishedProfileCompileInput {
session_id: "session_002".to_string(),
profile_id: "profile_002".to_string(),
owner_user_id: "user_002".to_string(),
draft_profile_json: r#"{
"name":"",
"subtitle":"",
"summary":"退",
"playableNpcs":[],
"storyNpcs":[],
"landmarks":[{"id":"landmark-1","name":"","imageSrc":"/generated/landmark-cover.png"}]
}"#
.to_string(),
legacy_result_profile_json: None,
setting_text: "被潮沟切开的荒湾".to_string(),
author_display_name: "玩家二号".to_string(),
updated_at_micros: 84,
},
)
.expect("compile should succeed");
assert_eq!(snapshot.theme_mode, CustomWorldThemeMode::Mythic);
assert_eq!(
snapshot.cover_image_src.as_deref(),
Some("/generated/landmark-cover.png")
);
}
}