Merge remote-tracking branch 'origin/master' into codex/wooden-fish-template

This commit is contained in:
2026-05-22 04:00:52 +08:00
121 changed files with 10876 additions and 3477 deletions

View File

@@ -2562,6 +2562,18 @@ fn upsert_nested_result_profile_id(
}
}
fn resolve_publish_world_setting_text(
payload: &JsonMap<String, JsonValue>,
draft_profile: &JsonMap<String, JsonValue>,
session: &CustomWorldAgentSession,
) -> String {
module_custom_world::resolve_custom_world_publish_setting_text(
payload,
draft_profile,
&session.seed_text,
)
}
fn is_same_agent_draft_profile_candidate(
row: &CustomWorldProfile,
owner_user_id: &str,
@@ -2581,13 +2593,10 @@ fn execute_publish_world_action(
) -> Result<CustomWorldAgentOperationSnapshot, String> {
ensure_publishable_stage(session.stage, "publish_world")?;
let draft_profile =
if let Some(explicit) = payload.get("draftProfile").and_then(JsonValue::as_object) {
explicit.clone()
} else {
parse_optional_session_object(session.draft_profile_json.as_deref())
.ok_or_else(|| "publish_world requires draft_profile_json".to_string())?
};
// 中文注释:发布动作不再信任前端携带的 draftProfile
// 点击发布前,结果页 profile 必须先通过 sync_result_profile 写回
// custom_world_agent_session.draft_profile_json正式发布只读取这份会话真相。
let draft_profile = read_publish_world_draft_profile_from_session(session)?;
let gate = summarize_publish_gate_from_json(
&session.session_id,
session.stage,
@@ -2601,24 +2610,9 @@ fn execute_publish_world_action(
));
}
let profile_id = payload
.get("profileId")
.and_then(JsonValue::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.unwrap_or_else(|| gate.profile_id.clone());
let setting_text = payload
.get("settingText")
.and_then(JsonValue::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.unwrap_or_else(|| session.seed_text.clone());
let legacy_result_profile_json = payload
.get("legacyResultProfile")
.map(serialize_json_value)
.transpose()?;
let profile_id = gate.profile_id.clone();
let setting_text = resolve_publish_world_setting_text(payload, &draft_profile, session);
let legacy_result_profile_json = None;
let author_public_user_code = read_optional_text_field(payload, &["authorPublicUserCode"])
.unwrap_or_else(|| build_public_user_code_from_owner_user_id(&session.owner_user_id));
let author_display_name = read_optional_text_field(payload, &["authorDisplayName"])
@@ -2663,6 +2657,13 @@ fn execute_publish_world_action(
Ok(build_custom_world_agent_operation_snapshot(&operation))
}
fn read_publish_world_draft_profile_from_session(
session: &CustomWorldAgentSession,
) -> Result<JsonMap<String, JsonValue>, String> {
parse_optional_session_object(session.draft_profile_json.as_deref())
.ok_or_else(|| "publish_world requires draft_profile_json".to_string())
}
fn execute_revert_checkpoint_action(
ctx: &ReducerContext,
session: &CustomWorldAgentSession,
@@ -5250,6 +5251,26 @@ mod tests {
);
}
#[test]
fn publish_world_draft_profile_comes_from_session_not_payload() {
let session = build_test_custom_world_agent_session(
"seed",
RpgAgentStage::ReadyToPublish,
Some(r#"{"id":"saved-profile","name":"已保存草稿"}"#),
);
let draft_profile =
read_publish_world_draft_profile_from_session(&session).expect("session draft exists");
assert_eq!(
draft_profile.get("id").and_then(JsonValue::as_str),
Some("saved-profile")
);
assert_eq!(
draft_profile.get("name").and_then(JsonValue::as_str),
Some("已保存草稿")
);
}
#[test]
fn custom_world_agent_session_direct_work_content_ignores_empty_created_session() {
let empty_session =

View File

@@ -1293,6 +1293,12 @@ fn select_puzzle_cover_image_tx(
ui_background_prompt: target_level.ui_background_prompt,
ui_background_image_src: target_level.ui_background_image_src,
ui_background_image_object_key: target_level.ui_background_image_object_key,
level_scene_image_src: target_level.level_scene_image_src,
level_scene_image_object_key: target_level.level_scene_image_object_key,
ui_spritesheet_image_src: target_level.ui_spritesheet_image_src,
ui_spritesheet_image_object_key: target_level.ui_spritesheet_image_object_key,
level_background_image_src: target_level.level_background_image_src,
level_background_image_object_key: target_level.level_background_image_object_key,
background_music: target_level.background_music,
candidates: selected_level_draft.candidates,
selected_candidate_id: selected_level_draft.selected_candidate_id,
@@ -2636,6 +2642,12 @@ fn build_profile_levels_from_row(
ui_background_prompt: None,
ui_background_image_src: None,
ui_background_image_object_key: None,
level_scene_image_src: None,
level_scene_image_object_key: None,
ui_spritesheet_image_src: None,
ui_spritesheet_image_object_key: None,
level_background_image_src: None,
level_background_image_object_key: None,
background_music: None,
candidates: Vec::new(),
selected_candidate_id: None,

View File

@@ -179,6 +179,7 @@ fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) {
}
}
migrate_rpg_entry_from_old_hidden_default(ctx, now);
migrate_visual_novel_entry_from_old_visible_default(ctx, now);
migrate_coming_soon_entry_from_old_open_default(
ctx,
@@ -195,6 +196,36 @@ fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) {
migrate_wooden_fish_entry_from_old_puzzle_image_default(ctx, now);
}
fn migrate_rpg_entry_from_old_hidden_default(ctx: &ReducerContext, now: Timestamp) {
let id = "rpg".to_string();
let Some(row) = ctx.db.creation_entry_type_config().id().find(&id) else {
return;
};
// 中文注释:只开放历史默认隐藏的 RPG 入口,不覆盖后台入口开关后续手动配置。
let still_old_hidden_default = row.title == "文字冒险"
&& row.subtitle == "经典 RPG 体验"
&& row.badge == "内测"
&& row.image_src == "/creation-type-references/rpg.webp"
&& !row.visible
&& row.open
&& row.sort_order == 10;
if !still_old_hidden_default {
return;
}
ctx.db
.creation_entry_type_config()
.id()
.update(CreationEntryTypeConfig {
badge: "可创建".to_string(),
visible: true,
open: true,
updated_at: now,
..row
});
}
fn migrate_visual_novel_entry_from_old_visible_default(ctx: &ReducerContext, now: Timestamp) {
let id = "visual-novel".to_string();
let Some(row) = ctx.db.creation_entry_type_config().id().find(&id) else {