Merge remote-tracking branch 'origin/master' into codex/bark-battle

This commit is contained in:
kdletters
2026-05-22 05:12:29 +08:00
275 changed files with 29199 additions and 41360 deletions

View File

@@ -18,6 +18,7 @@ module-big-fish = { workspace = true, features = ["spacetime-types"] }
module-combat = { workspace = true, features = ["spacetime-types"] }
module-inventory = { workspace = true, features = ["spacetime-types"] }
module-custom-world = { workspace = true, features = ["spacetime-types"] }
module-jump-hop = { workspace = true, features = ["spacetime-types"] }
module-match3d = { workspace = true }
module-npc = { workspace = true, features = ["spacetime-types"] }
module-puzzle = { workspace = true, features = ["spacetime-types"] }

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 =

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
use crate::*;
#[spacetimedb::table(
accessor = jump_hop_agent_session,
index(accessor = by_jump_hop_agent_session_owner_user_id, btree(columns = [owner_user_id]))
)]
pub struct JumpHopAgentSessionRow {
#[primary_key]
pub(crate) session_id: String,
pub(crate) owner_user_id: String,
pub(crate) seed_text: String,
pub(crate) current_turn: u32,
pub(crate) progress_percent: u32,
pub(crate) stage: String,
pub(crate) config_json: String,
pub(crate) draft_json: String,
pub(crate) last_assistant_reply: String,
pub(crate) published_profile_id: String,
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
}
#[spacetimedb::table(
accessor = jump_hop_work_profile,
index(accessor = by_jump_hop_work_owner_user_id, btree(columns = [owner_user_id])),
index(accessor = by_jump_hop_work_publication_status, btree(columns = [publication_status]))
)]
pub struct JumpHopWorkProfileRow {
#[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_tags_json: String,
pub(crate) difficulty: String,
pub(crate) style_preset: String,
pub(crate) character_prompt: String,
pub(crate) tile_prompt: String,
pub(crate) end_mood_prompt: String,
pub(crate) character_asset_json: String,
pub(crate) tile_atlas_asset_json: String,
pub(crate) tile_assets_json: String,
pub(crate) path_json: String,
pub(crate) cover_image_src: String,
pub(crate) cover_composite: 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>,
}
#[spacetimedb::table(
accessor = jump_hop_runtime_run,
index(accessor = by_jump_hop_run_owner_user_id, btree(columns = [owner_user_id])),
index(accessor = by_jump_hop_run_profile_id, btree(columns = [profile_id]))
)]
pub struct JumpHopRuntimeRunRow {
#[primary_key]
pub(crate) run_id: String,
pub(crate) owner_user_id: String,
pub(crate) profile_id: String,
pub(crate) status: String,
pub(crate) started_at_ms: i64,
pub(crate) finished_at_ms: i64,
pub(crate) current_platform_index: u32,
pub(crate) score: u32,
pub(crate) combo: u32,
pub(crate) snapshot_json: String,
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
}
#[spacetimedb::table(
accessor = jump_hop_event,
index(accessor = by_jump_hop_event_profile_id, btree(columns = [profile_id])),
index(accessor = by_jump_hop_event_run_id, btree(columns = [run_id]))
)]
pub struct JumpHopEventRow {
#[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,261 @@
use crate::*;
use serde::{Deserialize, Serialize};
pub const JUMP_HOP_TEMPLATE_ID: &str = "jump-hop";
pub const JUMP_HOP_TEMPLATE_NAME: &str = "跳一跳";
pub const JUMP_HOP_STYLE_MINIMAL_BLOCKS: &str = "minimal-blocks";
pub const JUMP_HOP_STAGE_COLLECTING: &str = "Collecting";
pub const JUMP_HOP_STAGE_DRAFT_COMPILED: &str = "DraftCompiled";
pub const JUMP_HOP_STAGE_PUBLISHED: &str = "Published";
pub const JUMP_HOP_PUBLICATION_DRAFT: &str = "Draft";
pub const JUMP_HOP_PUBLICATION_PUBLISHED: &str = "Published";
pub const JUMP_HOP_GENERATION_DRAFT: &str = "draft";
pub const JUMP_HOP_GENERATION_READY: &str = "ready";
pub const JUMP_HOP_EVENT_RUN_STARTED: &str = "run-started";
pub const JUMP_HOP_EVENT_RUN_RESTARTED: &str = "run-restarted";
pub const JUMP_HOP_EVENT_JUMP: &str = "jump";
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopAgentSessionCreateInput {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub work_title: String,
pub work_description: String,
pub theme_tags_json: Option<String>,
pub welcome_message_text: String,
pub config_json: Option<String>,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopAgentSessionGetInput {
pub session_id: String,
pub owner_user_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopDraftCompileInput {
pub session_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub author_display_name: String,
pub seed_text: String,
pub work_title: String,
pub work_description: String,
pub theme_tags_json: Option<String>,
pub theme_text: Option<String>,
pub difficulty: Option<String>,
pub style_preset: Option<String>,
pub character_prompt: Option<String>,
pub tile_prompt: Option<String>,
pub end_mood_prompt: Option<String>,
pub character_asset_json: Option<String>,
pub tile_atlas_asset_json: Option<String>,
pub tile_assets_json: Option<String>,
pub cover_composite: Option<String>,
pub generation_status: Option<String>,
pub compiled_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopWorkUpdateInput {
pub profile_id: String,
pub owner_user_id: String,
pub work_title: String,
pub work_description: String,
pub theme_tags_json: String,
pub difficulty: Option<String>,
pub style_preset: Option<String>,
pub cover_image_src: Option<String>,
pub cover_composite: Option<String>,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopWorkPublishInput {
pub profile_id: String,
pub owner_user_id: String,
pub published_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopWorksListInput {
pub owner_user_id: String,
pub published_only: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopWorkGetInput {
pub profile_id: String,
pub owner_user_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopRunStartInput {
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 JumpHopRunGetInput {
pub run_id: String,
pub owner_user_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopRunJumpInput {
pub run_id: String,
pub owner_user_id: String,
pub charge_ms: u32,
pub client_event_id: String,
pub jumped_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
pub struct JumpHopRunRestartInput {
pub source_run_id: String,
pub next_run_id: String,
pub owner_user_id: String,
pub client_action_id: String,
pub restarted_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct JumpHopAgentSessionProcedureResult {
pub ok: bool,
pub session: Option<JumpHopAgentSessionSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct JumpHopWorkProcedureResult {
pub ok: bool,
pub work: Option<JumpHopWorkSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct JumpHopWorksProcedureResult {
pub ok: bool,
pub items: Vec<JumpHopWorkSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, SpacetimeType)]
pub struct JumpHopRunProcedureResult {
pub ok: bool,
pub run: Option<module_jump_hop::JumpHopRunSnapshot>,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct JumpHopCreatorConfigSnapshot {
pub theme_text: String,
pub difficulty: String,
pub style_preset: String,
pub character_prompt: String,
pub tile_prompt: String,
#[serde(default)]
pub end_mood_prompt: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct JumpHopCharacterAssetSnapshot {
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, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct JumpHopTileAssetSnapshot {
pub tile_type: String,
pub image_src: String,
pub image_object_key: String,
pub asset_object_id: String,
pub source_atlas_cell: String,
pub visual_width: u32,
pub visual_height: u32,
pub top_surface_radius: f32,
pub landing_radius: f32,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct JumpHopDraftSnapshot {
pub template_id: String,
pub template_name: String,
pub profile_id: Option<String>,
pub work_title: String,
pub work_description: String,
pub theme_tags: Vec<String>,
pub difficulty: String,
pub style_preset: String,
pub character_prompt: String,
pub tile_prompt: String,
pub end_mood_prompt: Option<String>,
pub character_asset: Option<JumpHopCharacterAssetSnapshot>,
pub tile_atlas_asset: Option<JumpHopCharacterAssetSnapshot>,
pub tile_assets: Vec<JumpHopTileAssetSnapshot>,
pub path: Option<module_jump_hop::JumpHopPath>,
pub cover_composite: Option<String>,
pub generation_status: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)]
#[serde(rename_all = "camelCase")]
pub struct JumpHopAgentSessionSnapshot {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub config: JumpHopCreatorConfigSnapshot,
pub draft: Option<JumpHopDraftSnapshot>,
pub last_assistant_reply: String,
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 JumpHopWorkSnapshot {
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_tags: Vec<String>,
pub difficulty: String,
pub style_preset: String,
pub character_prompt: String,
pub tile_prompt: String,
pub end_mood_prompt: Option<String>,
pub character_asset: Option<JumpHopCharacterAssetSnapshot>,
pub tile_atlas_asset: Option<JumpHopCharacterAssetSnapshot>,
pub tile_assets: Vec<JumpHopTileAssetSnapshot>,
pub path: module_jump_hop::JumpHopPath,
pub cover_image_src: String,
pub cover_composite: 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>,
}

View File

@@ -8,6 +8,7 @@ pub use module_big_fish::*;
pub use module_combat::*;
pub use module_custom_world::*;
pub use module_inventory::*;
pub use module_jump_hop::*;
pub use module_npc::*;
pub use module_progression::*;
pub use module_quest::*;
@@ -29,6 +30,7 @@ mod custom_world;
mod domain_types;
mod entry;
mod gameplay;
mod jump_hop;
mod match3d;
mod migration;
mod puzzle;
@@ -45,6 +47,7 @@ pub use custom_world::*;
pub use domain_types::*;
pub use entry::*;
pub use gameplay::*;
pub use jump_hop::*;
pub use match3d::*;
pub use migration::*;
pub use runtime::*;

View File

@@ -12,6 +12,9 @@ use crate::bark_battle::tables::{
bark_battle_work_stats_projection,
};
use crate::big_fish::big_fish_runtime_run;
use crate::jump_hop::tables::{
jump_hop_agent_session, jump_hop_event, jump_hop_runtime_run, jump_hop_work_profile,
};
use crate::match3d::tables::{
match3d_agent_message, match3d_agent_session, match3d_runtime_run, match3d_work_profile,
};
@@ -234,6 +237,10 @@ macro_rules! migration_tables {
match3d_agent_message,
match3d_work_profile,
match3d_runtime_run,
jump_hop_agent_session,
jump_hop_work_profile,
jump_hop_runtime_run,
jump_hop_event,
square_hole_agent_session,
square_hole_agent_message,
square_hole_work_profile,

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_bark_battle_entry_to_open_default(ctx, now);
migrate_coming_soon_entry_from_old_open_default(