refactor: split large modules and normalize rust layout

This commit is contained in:
kdletters
2026-05-18 19:40:14 +08:00
parent 472a47eae7
commit 269f35cecf
51 changed files with 17492 additions and 17169 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
use super::*;
use crate::mapper::{
custom_world::{format_ai_result_reference_kind, format_ai_task_kind},
inventory::map_ai_result_reference_kind,
npc::map_ai_task_kind,
};
impl From<DomainAiTaskCreateInput> for AiTaskCreateInput {
fn from(input: DomainAiTaskCreateInput) -> Self {
Self {
task_id: input.task_id,
task_kind: map_ai_task_kind(input.task_kind),
owner_user_id: input.owner_user_id,
request_label: input.request_label,
source_module: input.source_module,
source_entity_id: input.source_entity_id,
request_payload_json: input.request_payload_json,
stages: input.stages.into_iter().map(Into::into).collect(),
created_at_micros: input.created_at_micros,
}
}
}
impl From<DomainAiTaskStartInput> for AiTaskStartInput {
fn from(input: DomainAiTaskStartInput) -> Self {
Self {
task_id: input.task_id,
started_at_micros: input.started_at_micros,
}
}
}
impl From<DomainAiTaskStageStartInput> for AiTaskStageStartInput {
fn from(input: DomainAiTaskStageStartInput) -> Self {
Self {
task_id: input.task_id,
stage_kind: map_ai_task_stage_kind(input.stage_kind),
started_at_micros: input.started_at_micros,
}
}
}
impl From<DomainAiTextChunkAppendInput> for AiTextChunkAppendInput {
fn from(input: DomainAiTextChunkAppendInput) -> Self {
Self {
task_id: input.task_id,
stage_kind: map_ai_task_stage_kind(input.stage_kind),
sequence: input.sequence,
delta_text: input.delta_text,
created_at_micros: input.created_at_micros,
}
}
}
impl From<DomainAiStageCompletionInput> for AiStageCompletionInput {
fn from(input: DomainAiStageCompletionInput) -> Self {
Self {
task_id: input.task_id,
stage_kind: map_ai_task_stage_kind(input.stage_kind),
text_output: input.text_output,
structured_payload_json: input.structured_payload_json,
warning_messages: input.warning_messages,
completed_at_micros: input.completed_at_micros,
}
}
}
impl From<DomainAiResultReferenceInput> for AiResultReferenceInput {
fn from(input: DomainAiResultReferenceInput) -> Self {
Self {
task_id: input.task_id,
reference_kind: map_ai_result_reference_kind(input.reference_kind),
reference_id: input.reference_id,
label: input.label,
created_at_micros: input.created_at_micros,
}
}
}
impl From<DomainAiTaskFinishInput> for AiTaskFinishInput {
fn from(input: DomainAiTaskFinishInput) -> Self {
Self {
task_id: input.task_id,
completed_at_micros: input.completed_at_micros,
}
}
}
impl From<DomainAiTaskFailureInput> for AiTaskFailureInput {
fn from(input: DomainAiTaskFailureInput) -> Self {
Self {
task_id: input.task_id,
failure_message: input.failure_message,
completed_at_micros: input.completed_at_micros,
}
}
}
impl From<DomainAiTaskCancelInput> for AiTaskCancelInput {
fn from(input: DomainAiTaskCancelInput) -> Self {
Self {
task_id: input.task_id,
completed_at_micros: input.completed_at_micros,
}
}
}
impl From<DomainAiTaskStageBlueprint> for AiTaskStageBlueprint {
fn from(blueprint: DomainAiTaskStageBlueprint) -> Self {
Self {
stage_kind: map_ai_task_stage_kind(blueprint.stage_kind),
label: blueprint.label,
detail: blueprint.detail,
order: blueprint.order,
}
}
}
pub(crate) fn map_ai_task_procedure_result(
result: AiTaskProcedureResult,
) -> Result<AiTaskMutationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let task = result
.task
.ok_or_else(|| SpacetimeClientError::missing_snapshot("ai_task 快照"))?;
Ok(AiTaskMutationRecord {
task: map_ai_task_snapshot(task),
text_chunk: result.text_chunk.map(map_ai_text_chunk_snapshot),
})
}
pub(crate) fn map_ai_task_snapshot(snapshot: AiTaskSnapshot) -> AiTaskRecord {
AiTaskRecord {
task_id: snapshot.task_id,
task_kind: format_ai_task_kind(snapshot.task_kind).to_string(),
owner_user_id: snapshot.owner_user_id,
request_label: snapshot.request_label,
source_module: snapshot.source_module,
source_entity_id: snapshot.source_entity_id,
request_payload_json: snapshot.request_payload_json,
status: format_ai_task_status(snapshot.status).to_string(),
failure_message: snapshot.failure_message,
stages: snapshot
.stages
.into_iter()
.map(map_ai_task_stage_snapshot)
.collect(),
result_references: snapshot
.result_references
.into_iter()
.map(map_ai_result_reference_snapshot)
.collect(),
latest_text_output: snapshot.latest_text_output,
latest_structured_payload_json: snapshot.latest_structured_payload_json,
version: snapshot.version,
created_at: format_timestamp_micros(snapshot.created_at_micros),
started_at: snapshot.started_at_micros.map(format_timestamp_micros),
completed_at: snapshot.completed_at_micros.map(format_timestamp_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
pub(crate) fn map_ai_task_stage_snapshot(snapshot: AiTaskStageSnapshot) -> AiTaskStageRecord {
AiTaskStageRecord {
stage_kind: format_ai_task_stage_kind(snapshot.stage_kind).to_string(),
label: snapshot.label,
detail: snapshot.detail,
order: snapshot.order,
status: format_ai_task_stage_status(snapshot.status).to_string(),
text_output: snapshot.text_output,
structured_payload_json: snapshot.structured_payload_json,
warning_messages: snapshot.warning_messages,
started_at: snapshot.started_at_micros.map(format_timestamp_micros),
completed_at: snapshot.completed_at_micros.map(format_timestamp_micros),
}
}
pub(crate) fn map_ai_text_chunk_snapshot(snapshot: AiTextChunkSnapshot) -> AiTextChunkRecord {
AiTextChunkRecord {
chunk_id: snapshot.chunk_id,
task_id: snapshot.task_id,
stage_kind: format_ai_task_stage_kind(snapshot.stage_kind).to_string(),
sequence: snapshot.sequence,
delta_text: snapshot.delta_text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
pub(crate) fn map_ai_result_reference_snapshot(
snapshot: AiResultReferenceSnapshot,
) -> AiResultReferenceRecord {
AiResultReferenceRecord {
result_ref_id: snapshot.result_ref_id,
task_id: snapshot.task_id,
reference_kind: format_ai_result_reference_kind(snapshot.reference_kind).to_string(),
reference_id: snapshot.reference_id,
label: snapshot.label,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
pub(crate) fn map_ai_task_stage_kind(value: DomainAiTaskStageKind) -> AiTaskStageKind {
match value {
DomainAiTaskStageKind::PreparePrompt => AiTaskStageKind::PreparePrompt,
DomainAiTaskStageKind::RequestModel => AiTaskStageKind::RequestModel,
DomainAiTaskStageKind::RepairResponse => AiTaskStageKind::RepairResponse,
DomainAiTaskStageKind::NormalizeResult => AiTaskStageKind::NormalizeResult,
DomainAiTaskStageKind::PersistResult => AiTaskStageKind::PersistResult,
}
}
pub(crate) fn format_ai_task_status(value: AiTaskStatus) -> &'static str {
match value {
AiTaskStatus::Pending => "pending",
AiTaskStatus::Running => "running",
AiTaskStatus::Completed => "completed",
AiTaskStatus::Failed => "failed",
AiTaskStatus::Cancelled => "cancelled",
}
}
pub(crate) fn format_ai_task_stage_kind(value: AiTaskStageKind) -> &'static str {
match value {
AiTaskStageKind::PreparePrompt => "prepare_prompt",
AiTaskStageKind::RequestModel => "request_model",
AiTaskStageKind::RepairResponse => "repair_response",
AiTaskStageKind::NormalizeResult => "normalize_result",
AiTaskStageKind::PersistResult => "persist_result",
}
}
pub(crate) fn format_ai_task_stage_status(value: AiTaskStageStatus) -> &'static str {
match value {
AiTaskStageStatus::Pending => "pending",
AiTaskStageStatus::Running => "running",
AiTaskStageStatus::Completed => "completed",
AiTaskStageStatus::Skipped => "skipped",
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AiTaskStageRecord {
pub stage_kind: String,
pub label: String,
pub detail: String,
pub order: u32,
pub status: String,
pub text_output: Option<String>,
pub structured_payload_json: Option<String>,
pub warning_messages: Vec<String>,
pub started_at: Option<String>,
pub completed_at: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AiResultReferenceRecord {
pub result_ref_id: String,
pub task_id: String,
pub reference_kind: String,
pub reference_id: String,
pub label: Option<String>,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AiTextChunkRecord {
pub chunk_id: String,
pub task_id: String,
pub stage_kind: String,
pub sequence: u32,
pub delta_text: String,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AiTaskRecord {
pub task_id: String,
pub task_kind: String,
pub owner_user_id: String,
pub request_label: String,
pub source_module: String,
pub source_entity_id: Option<String>,
pub request_payload_json: Option<String>,
pub status: String,
pub failure_message: Option<String>,
pub stages: Vec<AiTaskStageRecord>,
pub result_references: Vec<AiResultReferenceRecord>,
pub latest_text_output: Option<String>,
pub latest_structured_payload_json: Option<String>,
pub version: u32,
pub created_at: String,
pub started_at: Option<String>,
pub completed_at: Option<String>,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AiTaskMutationRecord {
pub task: AiTaskRecord,
pub text_chunk: Option<AiTextChunkRecord>,
}

View File

@@ -0,0 +1,382 @@
use super::*;
impl From<module_assets::AssetEntityBindingInput> for AssetEntityBindingInput {
fn from(input: module_assets::AssetEntityBindingInput) -> Self {
Self {
binding_id: input.binding_id,
asset_object_id: input.asset_object_id,
entity_kind: input.entity_kind,
entity_id: input.entity_id,
slot: input.slot,
asset_kind: input.asset_kind,
owner_user_id: input.owner_user_id,
profile_id: input.profile_id,
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<module_assets::AssetObjectUpsertInput> for AssetObjectUpsertInput {
fn from(input: module_assets::AssetObjectUpsertInput) -> Self {
Self {
asset_object_id: input.asset_object_id,
bucket: input.bucket,
object_key: input.object_key,
access_policy: map_access_policy(input.access_policy),
content_type: input.content_type,
content_length: input.content_length,
content_hash: input.content_hash,
version: input.version,
source_job_id: input.source_job_id,
owner_user_id: input.owner_user_id,
profile_id: input.profile_id,
entity_id: input.entity_id,
asset_kind: input.asset_kind,
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<module_assets::AssetHistoryListInput> for AssetHistoryListInput {
fn from(input: module_assets::AssetHistoryListInput) -> Self {
Self {
asset_kind: input.asset_kind,
limit: input.limit,
}
}
}
pub(crate) fn map_procedure_result(
result: AssetObjectProcedureResult,
) -> Result<AssetObjectRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let snapshot = result
.record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("对象快照"))?;
Ok(build_asset_object_record(map_snapshot(snapshot)))
}
pub(crate) fn map_entity_binding_procedure_result(
result: AssetEntityBindingProcedureResult,
) -> Result<AssetEntityBindingRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let snapshot = result
.record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("绑定快照"))?;
Ok(build_asset_entity_binding_record(
map_entity_binding_snapshot(snapshot),
))
}
pub(crate) fn map_entity_binding_snapshot(
snapshot: AssetEntityBindingSnapshot,
) -> module_assets::AssetEntityBindingSnapshot {
module_assets::AssetEntityBindingSnapshot {
binding_id: snapshot.binding_id,
asset_object_id: snapshot.asset_object_id,
entity_kind: snapshot.entity_kind,
entity_id: snapshot.entity_id,
slot: snapshot.slot,
asset_kind: snapshot.asset_kind,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_snapshot(
snapshot: AssetObjectUpsertSnapshot,
) -> module_assets::AssetObjectUpsertSnapshot {
module_assets::AssetObjectUpsertSnapshot {
asset_object_id: snapshot.asset_object_id,
bucket: snapshot.bucket,
object_key: snapshot.object_key,
access_policy: map_access_policy_back(snapshot.access_policy),
content_type: snapshot.content_type,
content_length: snapshot.content_length,
content_hash: snapshot.content_hash,
version: snapshot.version,
source_job_id: snapshot.source_job_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
entity_id: snapshot.entity_id,
asset_kind: snapshot.asset_kind,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_access_policy(
value: AssetObjectAccessPolicy,
) -> crate::module_bindings::AssetObjectAccessPolicy {
match value {
AssetObjectAccessPolicy::Private => {
crate::module_bindings::AssetObjectAccessPolicy::Private
}
AssetObjectAccessPolicy::PublicRead => {
crate::module_bindings::AssetObjectAccessPolicy::PublicRead
}
}
}
pub(crate) fn map_access_policy_back(
value: crate::module_bindings::AssetObjectAccessPolicy,
) -> AssetObjectAccessPolicy {
match value {
crate::module_bindings::AssetObjectAccessPolicy::Private => {
AssetObjectAccessPolicy::Private
}
crate::module_bindings::AssetObjectAccessPolicy::PublicRead => {
AssetObjectAccessPolicy::PublicRead
}
}
}
impl TryFrom<&str> for BigFishAssetKind {
type Error = SpacetimeClientError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.trim() {
"level_main_image" => Ok(Self::LevelMainImage),
"level_motion" => Ok(Self::LevelMotion),
"stage_background" => Ok(Self::StageBackground),
other => Err(SpacetimeClientError::Runtime(format!(
"big fish asset kind `{other}` 当前尚未支持"
))),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldDraftCardRecord {
pub card_id: String,
pub kind: String,
pub title: String,
pub subtitle: String,
pub summary: String,
pub status: String,
pub linked_ids: Vec<String>,
pub warning_count: u32,
pub asset_status: Option<String>,
pub asset_status_label: Option<String>,
pub detail_payload: Option<serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldDraftCardDetailRecord {
pub card_id: String,
pub kind: String,
pub title: String,
pub sections: Vec<CustomWorldDraftCardDetailSectionRecord>,
pub linked_ids: Vec<String>,
pub locked: bool,
pub editable: bool,
pub editable_section_ids: Vec<String>,
pub warning_messages: Vec<String>,
pub asset_status: Option<String>,
pub asset_status_label: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldAgentSessionRecord {
pub session_id: String,
pub seed_text: String,
pub current_turn: u32,
pub anchor_content: serde_json::Value,
pub progress_percent: u32,
pub last_assistant_reply: Option<String>,
pub stage: String,
pub focus_card_id: Option<String>,
pub creator_intent: serde_json::Value,
pub creator_intent_readiness: serde_json::Value,
pub anchor_pack: serde_json::Value,
pub lock_state: serde_json::Value,
pub draft_profile: serde_json::Value,
pub messages: Vec<CustomWorldAgentMessageRecord>,
pub draft_cards: Vec<CustomWorldDraftCardRecord>,
pub pending_clarifications: Vec<serde_json::Value>,
pub suggested_actions: Vec<serde_json::Value>,
pub recommended_replies: Vec<String>,
pub quality_findings: Vec<serde_json::Value>,
pub asset_coverage: serde_json::Value,
pub checkpoints: Vec<CustomWorldCheckpointRecord>,
pub supported_actions: Vec<CustomWorldSupportedActionRecord>,
pub publish_gate: Option<CustomWorldPublishGateRecord>,
pub result_preview: Option<serde_json::Value>,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldAgentSessionCreateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub welcome_message_id: String,
pub welcome_message_text: String,
pub anchor_content_json: String,
pub creator_intent_json: Option<String>,
pub creator_intent_readiness_json: String,
pub anchor_pack_json: Option<String>,
pub lock_state_json: Option<String>,
pub draft_profile_json: Option<String>,
pub pending_clarifications_json: String,
pub suggested_actions_json: String,
pub recommended_replies_json: String,
pub quality_findings_json: String,
pub asset_coverage_json: String,
pub checkpoints_json: String,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldAgentMessageFinalizeRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub operation_id: String,
pub assistant_message_id: Option<String>,
pub assistant_reply_text: Option<String>,
pub phase_label: String,
pub phase_detail: String,
pub operation_status: String,
pub operation_progress: u32,
pub stage: String,
pub progress_percent: u32,
pub focus_card_id: Option<String>,
pub anchor_content_json: String,
pub creator_intent_json: Option<String>,
pub creator_intent_readiness_json: String,
pub anchor_pack_json: Option<String>,
pub draft_profile_json: Option<String>,
pub pending_clarifications_json: String,
pub suggested_actions_json: String,
pub recommended_replies_json: String,
pub quality_findings_json: String,
pub asset_coverage_json: String,
pub error_message: Option<String>,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelAgentSessionCreateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub source_mode: String,
pub seed_text: String,
pub source_asset_ids_json: String,
pub welcome_message_id: String,
pub welcome_message_text: String,
pub draft_json: Option<String>,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelWorkUpdateRecordInput {
pub profile_id: String,
pub owner_user_id: String,
pub work_title: String,
pub work_description: String,
pub tags_json: String,
pub cover_image_src: Option<String>,
pub source_asset_ids_json: String,
pub draft_json: String,
pub publish_ready: bool,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VisualNovelAgentSessionRecord {
pub session_id: String,
pub owner_user_id: String,
pub source_mode: String,
pub status: String,
pub seed_text: String,
pub source_asset_ids: Vec<String>,
pub current_turn: u32,
pub progress_percent: u32,
pub messages: Vec<VisualNovelAgentMessageRecord>,
pub draft: Option<serde_json::Value>,
pub pending_action: Option<serde_json::Value>,
pub last_assistant_reply: Option<String>,
pub published_profile_id: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VisualNovelWorkProfileRecord {
pub work_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub source_session_id: Option<String>,
pub author_display_name: String,
pub work_title: String,
pub work_description: String,
pub tags: Vec<String>,
pub cover_image_src: Option<String>,
pub source_asset_ids: Vec<String>,
pub draft: serde_json::Value,
pub publication_status: String,
pub publish_ready: bool,
pub play_count: u32,
pub created_at: String,
pub updated_at: String,
pub published_at: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishAssetGenerateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub asset_kind: String,
pub level: Option<u32>,
pub motion_key: Option<String>,
pub asset_url: Option<String>,
pub generated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishAssetSlotRecord {
pub slot_id: String,
pub asset_kind: String,
pub level: Option<u32>,
pub motion_key: Option<String>,
pub status: String,
pub asset_url: Option<String>,
pub prompt_snapshot: String,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishAssetCoverageRecord {
pub level_main_image_ready_count: u32,
pub level_motion_ready_count: u32,
pub background_ready: bool,
pub required_level_count: u32,
pub publish_ready: bool,
pub blockers: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishSessionRecord {
pub session_id: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: BigFishAnchorPackRecord,
pub draft: Option<BigFishGameDraftRecord>,
pub asset_slots: Vec<BigFishAssetSlotRecord>,
pub asset_coverage: BigFishAssetCoverageRecord,
pub messages: Vec<BigFishAgentMessageRecord>,
pub last_assistant_reply: Option<String>,
pub publish_ready: bool,
pub updated_at: String,
}

View File

@@ -0,0 +1,42 @@
use super::*;
pub(crate) fn map_auth_store_snapshot_procedure_result(
result: AuthStoreSnapshotProcedureResult,
) -> Result<AuthStoreSnapshotRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let record = result
.record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("认证快照"))?;
Ok(map_auth_store_snapshot_record(record))
}
pub(crate) fn map_auth_store_snapshot_record(
record: crate::module_bindings::AuthStoreSnapshotRecord,
) -> crate::AuthStoreSnapshotRecord {
crate::AuthStoreSnapshotRecord {
snapshot_json: record.snapshot_json,
updated_at_micros: record.updated_at_micros,
}
}
pub(crate) fn map_auth_store_snapshot_import_procedure_result(
result: AuthStoreSnapshotImportProcedureResult,
) -> Result<AuthStoreSnapshotImportRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let record = result
.record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("认证快照导入结果"))?;
Ok(AuthStoreSnapshotImportRecord {
imported_user_count: record.imported_user_count,
imported_identity_count: record.imported_identity_count,
imported_refresh_session_count: record.imported_refresh_session_count,
})
}

View File

@@ -0,0 +1,94 @@
use super::*;
pub(crate) fn map_bark_battle_draft_config_procedure_result(
result: BarkBattleProcedureResult,
) -> Result<BarkBattleDraftConfigRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.draft_config
.ok_or_else(|| SpacetimeClientError::missing_snapshot("Bark Battle draft config"))
.map(bark_battle_draft_config_to_value)
}
pub(crate) fn map_bark_battle_runtime_config_procedure_result(
result: BarkBattleProcedureResult,
) -> Result<BarkBattleRuntimeConfigRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.runtime_config
.ok_or_else(|| SpacetimeClientError::missing_snapshot("Bark Battle runtime config"))
.map(bark_battle_runtime_config_to_value)
}
pub(crate) fn map_bark_battle_run_procedure_result(
result: BarkBattleProcedureResult,
) -> Result<BarkBattleRunRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.run
.ok_or_else(|| SpacetimeClientError::missing_snapshot("Bark Battle run"))
.map(bark_battle_run_to_value)
}
fn bark_battle_draft_config_to_value(snapshot: BarkBattleDraftConfigSnapshot) -> serde_json::Value {
serde_json::json!({
"draftId": snapshot.draft_id,
"ownerUserId": snapshot.owner_user_id,
"workId": snapshot.work_id,
"configVersion": snapshot.config_version,
"rulesetVersion": snapshot.ruleset_version,
"difficultyPreset": snapshot.difficulty_preset,
"leaderboardEnabled": snapshot.leaderboard_enabled,
"configJson": snapshot.config_json,
"editorStateJson": snapshot.editor_state_json,
"createdAtMicros": snapshot.created_at_micros,
"updatedAtMicros": snapshot.updated_at_micros,
})
}
fn bark_battle_runtime_config_to_value(
snapshot: BarkBattleRuntimeConfigSnapshot,
) -> serde_json::Value {
serde_json::json!({
"workId": snapshot.work_id,
"ownerUserId": snapshot.owner_user_id,
"sourceDraftId": snapshot.source_draft_id,
"configVersion": snapshot.config_version,
"rulesetVersion": snapshot.ruleset_version,
"difficultyPreset": snapshot.difficulty_preset,
"leaderboardEnabled": snapshot.leaderboard_enabled,
"configJson": snapshot.config_json,
"publishedSnapshotJson": snapshot.published_snapshot_json,
"publishedAtMicros": snapshot.published_at_micros,
"updatedAtMicros": snapshot.updated_at_micros,
})
}
fn bark_battle_run_to_value(snapshot: BarkBattleRunSnapshot) -> serde_json::Value {
serde_json::json!({
"runId": snapshot.run_id,
"ownerUserId": snapshot.owner_user_id,
"workId": snapshot.work_id,
"configVersion": snapshot.config_version,
"rulesetVersion": snapshot.ruleset_version,
"difficultyPreset": snapshot.difficulty_preset,
"leaderboardEnabled": snapshot.leaderboard_enabled,
"status": snapshot.status,
"clientStartedAtMicros": snapshot.client_started_at_micros,
"serverStartedAtMicros": snapshot.server_started_at_micros,
"clientFinishedAtMicros": snapshot.client_finished_at_micros,
"serverFinishedAtMicros": snapshot.server_finished_at_micros,
"metricsJson": snapshot.metrics_json,
"serverResult": snapshot.server_result,
"validationStatus": snapshot.validation_status,
"antiCheatFlagsJson": snapshot.anti_cheat_flags_json,
"leaderboardScore": snapshot.leaderboard_score,
"scoreId": snapshot.score_id,
})
}

View File

@@ -0,0 +1,616 @@
use super::*;
pub(crate) fn map_big_fish_session_procedure_result(
result: BigFishSessionProcedureResult,
) -> Result<BigFishSessionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("big fish session 快照"))?;
Ok(map_big_fish_session_snapshot(session))
}
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_failed(result.error_message));
}
Ok(result
.items
.into_iter()
.map(map_big_fish_work_summary_snapshot)
.collect())
}
pub(crate) fn map_big_fish_run_procedure_result(
result: BigFishRunProcedureResult,
) -> Result<BigFishRuntimeRunRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run = result
.run
.ok_or_else(|| SpacetimeClientError::missing_snapshot("big fish run 快照"))?;
Ok(map_big_fish_runtime_snapshot(run))
}
pub(crate) fn map_big_fish_session_snapshot(
snapshot: BigFishSessionSnapshot,
) -> BigFishSessionRecord {
BigFishSessionRecord {
session_id: snapshot.session_id,
current_turn: snapshot.current_turn,
progress_percent: snapshot.progress_percent,
stage: format_big_fish_creation_stage(snapshot.stage).to_string(),
anchor_pack: map_big_fish_anchor_pack(snapshot.anchor_pack),
draft: snapshot.draft.map(map_big_fish_game_draft),
asset_slots: snapshot
.asset_slots
.into_iter()
.map(map_big_fish_asset_slot_snapshot)
.collect(),
asset_coverage: map_big_fish_asset_coverage(snapshot.asset_coverage),
messages: snapshot
.messages
.into_iter()
.map(map_big_fish_agent_message_snapshot)
.collect(),
last_assistant_reply: snapshot.last_assistant_reply,
publish_ready: snapshot.publish_ready,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
pub(crate) fn map_big_fish_anchor_pack(snapshot: BigFishAnchorPack) -> BigFishAnchorPackRecord {
BigFishAnchorPackRecord {
gameplay_promise: map_big_fish_anchor_item(snapshot.gameplay_promise),
ecology_visual_theme: map_big_fish_anchor_item(snapshot.ecology_visual_theme),
growth_ladder: map_big_fish_anchor_item(snapshot.growth_ladder),
risk_tempo: map_big_fish_anchor_item(snapshot.risk_tempo),
}
}
pub(crate) fn map_big_fish_anchor_item(snapshot: BigFishAnchorItem) -> BigFishAnchorItemRecord {
BigFishAnchorItemRecord {
key: snapshot.key,
label: snapshot.label,
value: snapshot.value,
status: format_big_fish_anchor_status(snapshot.status).to_string(),
}
}
pub(crate) fn map_big_fish_game_draft(snapshot: BigFishGameDraft) -> BigFishGameDraftRecord {
BigFishGameDraftRecord {
title: snapshot.title,
subtitle: snapshot.subtitle,
core_fun: snapshot.core_fun,
ecology_theme: snapshot.ecology_theme,
levels: snapshot
.levels
.into_iter()
.map(map_big_fish_level_blueprint)
.collect(),
background: map_big_fish_background_blueprint(snapshot.background),
runtime_params: map_big_fish_runtime_params(snapshot.runtime_params),
}
}
pub(crate) fn map_big_fish_level_blueprint(
snapshot: BigFishLevelBlueprint,
) -> BigFishLevelBlueprintRecord {
BigFishLevelBlueprintRecord {
level: snapshot.level,
name: snapshot.name,
one_line_fantasy: snapshot.one_line_fantasy,
text_description: snapshot.text_description,
silhouette_direction: snapshot.silhouette_direction,
size_ratio: snapshot.size_ratio,
visual_description: snapshot.visual_description,
visual_prompt_seed: snapshot.visual_prompt_seed,
idle_motion_description: snapshot.idle_motion_description,
move_motion_description: snapshot.move_motion_description,
motion_prompt_seed: snapshot.motion_prompt_seed,
merge_source_level: snapshot.merge_source_level,
prey_window: snapshot.prey_window,
threat_window: snapshot.threat_window,
is_final_level: snapshot.is_final_level,
}
}
pub(crate) fn map_big_fish_background_blueprint(
snapshot: BigFishBackgroundBlueprint,
) -> BigFishBackgroundBlueprintRecord {
BigFishBackgroundBlueprintRecord {
theme: snapshot.theme,
color_mood: snapshot.color_mood,
foreground_hints: snapshot.foreground_hints,
midground_composition: snapshot.midground_composition,
background_depth: snapshot.background_depth,
safe_play_area_hint: snapshot.safe_play_area_hint,
spawn_edge_hint: snapshot.spawn_edge_hint,
background_prompt_seed: snapshot.background_prompt_seed,
}
}
pub(crate) fn map_big_fish_runtime_params(
snapshot: BigFishRuntimeParams,
) -> BigFishRuntimeParamsRecord {
BigFishRuntimeParamsRecord {
level_count: snapshot.level_count,
merge_count_per_upgrade: snapshot.merge_count_per_upgrade,
spawn_target_count: snapshot.spawn_target_count,
leader_move_speed: snapshot.leader_move_speed,
follower_catch_up_speed: snapshot.follower_catch_up_speed,
offscreen_cull_seconds: snapshot.offscreen_cull_seconds,
prey_spawn_delta_levels: snapshot.prey_spawn_delta_levels,
threat_spawn_delta_levels: snapshot.threat_spawn_delta_levels,
win_level: snapshot.win_level,
}
}
pub(crate) fn map_big_fish_asset_slot_snapshot(
snapshot: BigFishAssetSlotSnapshot,
) -> BigFishAssetSlotRecord {
BigFishAssetSlotRecord {
slot_id: snapshot.slot_id,
asset_kind: format_big_fish_asset_kind(snapshot.asset_kind).to_string(),
level: snapshot.level,
motion_key: snapshot.motion_key,
status: format_big_fish_asset_status(snapshot.status).to_string(),
asset_url: snapshot.asset_url,
prompt_snapshot: snapshot.prompt_snapshot,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
pub(crate) fn map_big_fish_asset_coverage(
snapshot: BigFishAssetCoverage,
) -> BigFishAssetCoverageRecord {
BigFishAssetCoverageRecord {
level_main_image_ready_count: snapshot.level_main_image_ready_count,
level_motion_ready_count: snapshot.level_motion_ready_count,
background_ready: snapshot.background_ready,
required_level_count: snapshot.required_level_count,
publish_ready: snapshot.publish_ready,
blockers: snapshot.blockers,
}
}
pub(crate) fn map_big_fish_agent_message_snapshot(
snapshot: BigFishAgentMessageSnapshot,
) -> BigFishAgentMessageRecord {
BigFishAgentMessageRecord {
message_id: snapshot.message_id,
role: format_big_fish_agent_message_role(snapshot.role).to_string(),
kind: format_big_fish_agent_message_kind(snapshot.kind).to_string(),
text: snapshot.text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
pub(crate) fn map_big_fish_work_summary_snapshot(
snapshot: BigFishWorkSummarySnapshot,
) -> BigFishWorkSummaryRecord {
BigFishWorkSummaryRecord {
work_id: snapshot.work_id,
source_session_id: snapshot.source_session_id,
owner_user_id: snapshot.owner_user_id,
title: snapshot.title,
subtitle: snapshot.subtitle,
summary: snapshot.summary,
cover_image_src: snapshot.cover_image_src,
status: snapshot.status,
updated_at_micros: snapshot.updated_at_micros,
published_at_micros: snapshot.published_at_micros,
publish_ready: snapshot.publish_ready,
level_count: snapshot.level_count,
level_main_image_ready_count: snapshot.level_main_image_ready_count,
level_motion_ready_count: snapshot.level_motion_ready_count,
background_ready: snapshot.background_ready,
play_count: snapshot.play_count,
remix_count: snapshot.remix_count,
like_count: snapshot.like_count,
recent_play_count_7d: snapshot.recent_play_count_7_d,
}
}
pub(crate) fn map_big_fish_gallery_view_row(
row: BigFishWorkSummarySnapshot,
recent_play_count_7d: u32,
) -> BigFishWorkSummaryRecord {
let mut record = map_big_fish_work_summary_snapshot(row);
record.recent_play_count_7d = recent_play_count_7d;
record
}
pub(crate) fn map_big_fish_runtime_snapshot(
snapshot: BigFishRuntimeSnapshot,
) -> BigFishRuntimeRunRecord {
BigFishRuntimeRunRecord {
run_id: snapshot.run_id,
session_id: snapshot.session_id,
status: format_big_fish_run_status(snapshot.status).to_string(),
tick: snapshot.tick,
player_level: snapshot.player_level,
win_level: snapshot.win_level,
leader_entity_id: snapshot.leader_entity_id,
owned_entities: snapshot
.owned_entities
.into_iter()
.map(map_big_fish_runtime_entity_snapshot)
.collect(),
wild_entities: snapshot
.wild_entities
.into_iter()
.map(map_big_fish_runtime_entity_snapshot)
.collect(),
camera_center: map_big_fish_vector2(snapshot.camera_center),
last_input: map_big_fish_vector2(snapshot.last_input),
event_log: snapshot.event_log,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_big_fish_runtime_entity_snapshot(
snapshot: BigFishRuntimeEntitySnapshot,
) -> BigFishRuntimeEntityRecord {
BigFishRuntimeEntityRecord {
entity_id: snapshot.entity_id,
level: snapshot.level,
position: map_big_fish_vector2(snapshot.position),
radius: snapshot.radius,
offscreen_seconds: snapshot.offscreen_seconds,
}
}
fn map_big_fish_vector2(snapshot: BigFishVector2) -> BigFishVector2Record {
BigFishVector2Record {
x: snapshot.x,
y: snapshot.y,
}
}
pub(crate) fn parse_big_fish_creation_stage(
value: &str,
) -> Result<BigFishCreationStage, SpacetimeClientError> {
match value.trim() {
"collecting_anchors" => Ok(BigFishCreationStage::CollectingAnchors),
"draft_ready" => Ok(BigFishCreationStage::DraftReady),
"asset_refining" => Ok(BigFishCreationStage::AssetRefining),
"ready_to_publish" => Ok(BigFishCreationStage::ReadyToPublish),
"published" => Ok(BigFishCreationStage::Published),
other => Err(SpacetimeClientError::Runtime(format!(
"big fish creation stage `{other}` 当前尚未支持"
))),
}
}
pub(crate) fn format_big_fish_creation_stage(value: BigFishCreationStage) -> &'static str {
match value {
BigFishCreationStage::CollectingAnchors => "collecting_anchors",
BigFishCreationStage::DraftReady => "draft_ready",
BigFishCreationStage::AssetRefining => "asset_refining",
BigFishCreationStage::ReadyToPublish => "ready_to_publish",
BigFishCreationStage::Published => "published",
}
}
pub(crate) fn format_big_fish_anchor_status(value: BigFishAnchorStatus) -> &'static str {
match value {
BigFishAnchorStatus::Confirmed => "confirmed",
BigFishAnchorStatus::Inferred => "inferred",
BigFishAnchorStatus::Missing => "missing",
BigFishAnchorStatus::Locked => "locked",
}
}
pub(crate) fn format_big_fish_agent_message_role(value: BigFishAgentMessageRole) -> &'static str {
match value {
BigFishAgentMessageRole::User => "user",
BigFishAgentMessageRole::Assistant => "assistant",
BigFishAgentMessageRole::System => "system",
}
}
pub(crate) fn format_big_fish_agent_message_kind(value: BigFishAgentMessageKind) -> &'static str {
match value {
BigFishAgentMessageKind::Chat => "chat",
BigFishAgentMessageKind::Summary => "summary",
BigFishAgentMessageKind::ActionResult => "action_result",
BigFishAgentMessageKind::Warning => "warning",
}
}
pub(crate) fn format_big_fish_asset_kind(value: BigFishAssetKind) -> &'static str {
match value {
BigFishAssetKind::LevelMainImage => "level_main_image",
BigFishAssetKind::LevelMotion => "level_motion",
BigFishAssetKind::StageBackground => "stage_background",
}
}
pub(crate) fn format_big_fish_asset_status(value: BigFishAssetStatus) -> &'static str {
match value {
BigFishAssetStatus::Missing => "missing",
BigFishAssetStatus::Ready => "ready",
}
}
pub(crate) fn format_big_fish_run_status(value: BigFishRunStatus) -> &'static str {
match value {
BigFishRunStatus::Running => "running",
BigFishRunStatus::Won => "won",
BigFishRunStatus::Failed => "failed",
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct BigFishWorkSummaryRecord {
pub work_id: String,
pub source_session_id: String,
pub owner_user_id: String,
pub title: String,
pub subtitle: String,
pub summary: String,
pub cover_image_src: Option<String>,
pub status: String,
pub updated_at_micros: i64,
pub published_at_micros: Option<i64>,
pub publish_ready: bool,
pub level_count: u32,
pub level_main_image_ready_count: u32,
pub level_motion_ready_count: u32,
pub background_ready: bool,
pub play_count: u32,
pub remix_count: u32,
pub like_count: u32,
pub recent_play_count_7d: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn puzzle_works_mapper_keeps_typed_public_stat_fields() {
let result = PuzzleWorksProcedureResult {
ok: true,
items: vec![PuzzleWorkProfile {
work_id: "puzzle-work-1".to_string(),
profile_id: "puzzle-profile-1".to_string(),
owner_user_id: "user-1".to_string(),
source_session_id: None,
author_display_name: "测试作者".to_string(),
work_title: "雨夜拼图作品".to_string(),
work_description: "拼图作品说明".to_string(),
level_name: "雨夜拼图".to_string(),
summary: "公开作品摘要".to_string(),
theme_tags: vec!["雨夜".to_string(), "猫咪".to_string(), "神庙".to_string()],
cover_image_src: None,
cover_asset_id: None,
levels: Vec::new(),
publication_status: PuzzlePublicationStatus::Published,
updated_at_micros: 123000000,
published_at_micros: Some(123000000),
play_count: 11,
remix_count: 7,
like_count: 5,
recent_play_count_7_d: 3,
point_incentive_total_half_points: 4,
point_incentive_claimed_points: 2,
publish_ready: true,
anchor_pack: test_puzzle_anchor_pack(),
}],
error_message: None,
};
let items = map_puzzle_works_procedure_result(result)
.expect("typed puzzle works result 应能映射统计字段");
assert_eq!(items.len(), 1);
assert_eq!(items[0].play_count, 11);
assert_eq!(items[0].remix_count, 7);
assert_eq!(items[0].like_count, 5);
assert_eq!(items[0].recent_play_count_7d, 3);
}
#[test]
fn puzzle_run_mapper_maps_typed_timer_fields() {
let result = PuzzleRunProcedureResult {
ok: true,
run: Some(PuzzleRunSnapshot {
run_id: "puzzle-run-1".to_string(),
entry_profile_id: "puzzle-profile-1".to_string(),
cleared_level_count: 0,
current_level_index: 1,
current_grid_size: 3,
played_profile_ids: vec!["puzzle-profile-1".to_string()],
previous_level_tags: vec![
"雨夜".to_string(),
"猫咪".to_string(),
"神庙".to_string(),
],
current_level: Some(PuzzleRuntimeLevelSnapshot {
run_id: "puzzle-run-1".to_string(),
level_index: 1,
level_id: None,
grid_size: 3,
profile_id: "puzzle-profile-1".to_string(),
level_name: "雨夜拼图".to_string(),
author_display_name: "测试作者".to_string(),
theme_tags: vec!["雨夜".to_string(), "猫咪".to_string(), "神庙".to_string()],
cover_image_src: None,
ui_background_image_src: None,
ui_background_image_object_key: None,
background_music: None,
board: PuzzleBoardSnapshot {
rows: 3,
cols: 3,
pieces: vec![PuzzlePieceState {
piece_id: "piece-1".to_string(),
correct_row: 0,
correct_col: 0,
current_row: 0,
current_col: 0,
merged_group_id: None,
}],
merged_groups: Vec::new(),
selected_piece_id: None,
all_tiles_resolved: false,
},
status: PuzzleRuntimeLevelStatus::Playing,
started_at_ms: 0,
cleared_at_ms: None,
elapsed_ms: None,
time_limit_ms: 0,
remaining_ms: 0,
paused_accumulated_ms: 0,
pause_started_at_ms: None,
freeze_accumulated_ms: 0,
freeze_started_at_ms: None,
freeze_until_ms: None,
leaderboard_entries: Vec::new(),
}),
recommended_next_profile_id: None,
next_level_mode: "none".to_string(),
next_level_profile_id: None,
next_level_id: None,
recommended_next_works: Vec::new(),
leaderboard_entries: Vec::new(),
}),
error_message: None,
};
let run = map_puzzle_run_procedure_result(result)
.expect("typed puzzle run result 应能映射计时字段");
let level = run.current_level.expect("兼容后仍应保留当前关卡");
assert_eq!(run.run_id, "puzzle-run-1");
assert!(level.started_at_ms > 0);
assert_eq!(level.time_limit_ms, 0);
assert_eq!(level.remaining_ms, 0);
assert!(level.leaderboard_entries.is_empty());
}
#[test]
fn big_fish_works_mapper_uses_typed_owner_and_public_stats() {
let result = BigFishWorksProcedureResult {
ok: true,
items: vec![BigFishWorkSummarySnapshot {
work_id: "big-fish-work-session-1".to_string(),
source_session_id: "session-1".to_string(),
owner_user_id: "user-1".to_string(),
title: "深海草稿".to_string(),
subtitle: "副标题".to_string(),
summary: "摘要".to_string(),
cover_image_src: None,
status: "draft".to_string(),
updated_at_micros: 123,
publish_ready: false,
level_count: 8,
level_main_image_ready_count: 0,
level_motion_ready_count: 0,
background_ready: false,
play_count: 9,
remix_count: 4,
like_count: 2,
recent_play_count_7_d: 6,
published_at_micros: None,
}],
error_message: None,
};
let items = map_big_fish_works_procedure_result(result, Some("user-1"))
.expect("typed big fish works result 应能映射 owner 和统计字段");
assert_eq!(items.len(), 1);
assert_eq!(items[0].owner_user_id, "user-1");
assert_eq!(items[0].published_at_micros, None);
assert_eq!(items[0].play_count, 9);
assert_eq!(items[0].remix_count, 4);
assert_eq!(items[0].like_count, 2);
assert_eq!(items[0].recent_play_count_7d, 6);
}
#[test]
fn match3d_work_mapper_keeps_generated_item_assets_json() {
let result = Match3DWorkProcedureResult {
ok: true,
work: Some(Match3DWorkSnapshot {
profile_id: "match3d-profile-1".to_string(),
owner_user_id: "user-1".to_string(),
source_session_id: "match3d-session-1".to_string(),
author_display_name: "测试作者".to_string(),
game_name: "水果抓大鹅".to_string(),
theme_text: "水果".to_string(),
summary_text: "水果主题".to_string(),
tags: vec!["水果".to_string()],
cover_image_src: String::new(),
cover_asset_id: String::new(),
clear_count: 3,
difficulty: 3,
config: Match3DCreatorConfigSnapshot {
theme_text: "水果".to_string(),
reference_image_src: None,
clear_count: 3,
difficulty: 3,
asset_style_id: None,
asset_style_label: None,
asset_style_prompt: None,
generate_click_sound: false,
},
publication_status: "Draft".to_string(),
publish_ready: false,
play_count: 0,
updated_at_micros: 123000000,
published_at_micros: None,
generated_item_assets_json: Some(
r#"[{"itemId":"match3d-item-1","itemName":"草莓","imageSrc":"/generated-match3d-assets/session/profile/items/item/image.png","status":"image_ready"}]"#
.to_string(),
),
}),
error_message: None,
};
let item = map_match3d_work_procedure_result(result)
.expect("typed match3d work result 应保留生成素材 JSON");
assert_eq!(
item.generated_item_assets_json.as_deref(),
Some(
r#"[{"itemId":"match3d-item-1","itemName":"草莓","imageSrc":"/generated-match3d-assets/session/profile/items/item/image.png","status":"image_ready"}]"#
)
);
}
fn test_puzzle_anchor_pack() -> PuzzleAnchorPack {
PuzzleAnchorPack {
theme_promise: test_puzzle_anchor_item("themePromise", "题材承诺", "雨夜冒险"),
visual_subject: test_puzzle_anchor_item("visualSubject", "画面主体", "猫咪神庙"),
visual_mood: test_puzzle_anchor_item("visualMood", "视觉气质", "温暖"),
composition_hooks: test_puzzle_anchor_item("compositionHooks", "拼图记忆点", "灯光"),
tags_and_forbidden: test_puzzle_anchor_item(
"tagsAndForbidden",
"标签与禁忌",
"雨夜, 猫咪, 神庙",
),
}
}
fn test_puzzle_anchor_item(key: &str, label: &str, value: &str) -> PuzzleAnchorItem {
PuzzleAnchorItem {
key: key.to_string(),
label: label.to_string(),
value: value.to_string(),
status: PuzzleAnchorStatus::Inferred,
}
}
}

View File

@@ -0,0 +1,124 @@
use super::*;
impl From<DomainBattleStateQueryInput> for BattleStateQueryInput {
fn from(input: DomainBattleStateQueryInput) -> Self {
Self {
battle_state_id: input.battle_state_id,
}
}
}
impl From<DomainResolveCombatActionInput> for ResolveCombatActionInput {
fn from(input: DomainResolveCombatActionInput) -> Self {
Self {
battle_state_id: input.battle_state_id,
function_id: input.function_id,
action_text: input.action_text,
base_damage: input.base_damage,
mana_cost: input.mana_cost,
heal: input.heal,
mana_restore: input.mana_restore,
counter_multiplier_basis_points: input.counter_multiplier_basis_points,
updated_at_micros: input.updated_at_micros,
}
}
}
pub type BarkBattleDraftConfigRecord = serde_json::Value;
pub type BarkBattleRuntimeConfigRecord = serde_json::Value;
pub type BarkBattleRunRecord = serde_json::Value;
pub(crate) fn map_battle_state_procedure_result(
result: BattleStateProcedureResult,
) -> Result<BattleStateRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let snapshot = result
.snapshot
.ok_or_else(|| SpacetimeClientError::missing_snapshot("battle_state 快照"))?;
Ok(build_battle_state_record(map_battle_state_snapshot(
snapshot,
)))
}
pub(crate) fn map_resolve_combat_action_procedure_result(
result: ResolveCombatActionProcedureResult,
) -> Result<ResolveCombatActionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let action_result = result
.result
.ok_or_else(|| SpacetimeClientError::missing_snapshot("战斗结算结果"))?;
Ok(build_resolve_combat_action_record(
map_resolve_combat_action_result(action_result),
))
}
pub(crate) fn map_resolve_combat_action_result(
result: ResolveCombatActionResult,
) -> DomainResolveCombatActionResult {
DomainResolveCombatActionResult {
snapshot: map_battle_state_snapshot(result.snapshot),
damage_dealt: result.damage_dealt,
damage_taken: result.damage_taken,
outcome: map_combat_outcome(result.outcome),
}
}
pub(crate) fn map_battle_mode(value: DomainBattleMode) -> BattleMode {
match value {
DomainBattleMode::Fight => BattleMode::Fight,
DomainBattleMode::Spar => BattleMode::Spar,
}
}
pub(crate) fn map_battle_mode_back(value: BattleMode) -> DomainBattleMode {
match value {
BattleMode::Fight => DomainBattleMode::Fight,
BattleMode::Spar => DomainBattleMode::Spar,
}
}
pub(crate) fn map_battle_status(value: BattleStatus) -> DomainBattleStatus {
match value {
BattleStatus::Ongoing => DomainBattleStatus::Ongoing,
BattleStatus::Resolved => DomainBattleStatus::Resolved,
BattleStatus::Aborted => DomainBattleStatus::Aborted,
}
}
pub(crate) fn map_combat_outcome(value: CombatOutcome) -> DomainCombatOutcome {
match value {
CombatOutcome::Ongoing => DomainCombatOutcome::Ongoing,
CombatOutcome::Victory => DomainCombatOutcome::Victory,
CombatOutcome::SparComplete => DomainCombatOutcome::SparComplete,
CombatOutcome::Escaped => DomainCombatOutcome::Escaped,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolveCombatActionRecord {
pub battle_state: BattleStateRecord,
pub damage_dealt: i32,
pub damage_taken: i32,
pub outcome: String,
}
pub(crate) fn build_resolve_combat_action_record(
result: DomainResolveCombatActionResult,
) -> ResolveCombatActionRecord {
ResolveCombatActionRecord {
battle_state: build_battle_state_record(result.snapshot),
damage_dealt: result.damage_dealt,
damage_taken: result.damage_taken,
outcome: result.outcome.as_str().to_string(),
}
}

View File

@@ -0,0 +1,706 @@
use super::*;
impl From<CustomWorldPublishWorldRecordInput> for CustomWorldPublishWorldInput {
fn from(input: CustomWorldPublishWorldRecordInput) -> Self {
Self {
session_id: input.session_id,
profile_id: input.profile_id,
owner_user_id: input.owner_user_id,
public_work_code: input.public_work_code,
author_public_user_code: input.author_public_user_code,
draft_profile_json: input.draft_profile_json,
legacy_result_profile_json: input.legacy_result_profile_json,
setting_text: input.setting_text,
author_display_name: input.author_display_name,
published_at_micros: input.published_at_micros,
}
}
}
pub(crate) fn empty_string_to_none(value: String) -> Option<String> {
let trimmed = value.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
pub(crate) fn i64_to_u64_ms(value: i64) -> u64 {
value.max(0) as u64
}
pub(crate) fn parse_optional_json_value(
value: Option<&str>,
fallback: serde_json::Value,
label: &str,
) -> Result<serde_json::Value, SpacetimeClientError> {
match value.map(str::trim).filter(|value| !value.is_empty()) {
Some(value) => parse_json_value(value, label),
None => Ok(fallback),
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldLibraryMutationRecord {
pub entry: CustomWorldLibraryEntryRecord,
pub gallery_entry: Option<CustomWorldGalleryEntryRecord>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldPublishWorldRecord {
pub compiled_record: CustomWorldPublishedProfileCompileRecord,
pub entry: CustomWorldLibraryEntryRecord,
pub gallery_entry: Option<CustomWorldGalleryEntryRecord>,
pub session_stage: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldAgentMessageRecord {
pub message_id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
pub related_operation_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldAgentOperationRecord {
pub operation_id: String,
pub operation_type: String,
pub status: String,
pub phase_label: String,
pub phase_detail: String,
pub progress: u32,
pub error_message: Option<String>,
pub started_at_micros: i64,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldAgentOperationProgressRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub operation_id: String,
// SpacetimeDB 模块侧使用枚举存储操作类型,这里保留字符串给 API 层做轻量传参。
pub operation_type: String,
pub operation_status: String,
pub phase_label: String,
pub phase_detail: String,
pub operation_progress: u32,
pub error_message: Option<String>,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldSupportedActionRecord {
pub action: String,
pub enabled: bool,
pub reason: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldCheckpointRecord {
pub checkpoint_id: String,
pub created_at: String,
pub label: String,
}
// 兼容并行 custom world facade 中仍在使用的旧命名,避免本轮 module-npc 收口被无关改动阻塞。
pub type CustomWorldAgentCheckpointRecord = CustomWorldCheckpointRecord;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldResultPreviewBlockerRecord {
pub id: String,
pub code: String,
pub message: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldPublishGateRecord {
pub profile_id: String,
pub blockers: Vec<CustomWorldResultPreviewBlockerRecord>,
pub blocker_count: u32,
pub publish_ready: bool,
pub can_enter_world: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldDraftCardDetailSectionRecord {
pub section_id: String,
pub label: String,
pub value: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldProfileRemixRecordInput {
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,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldProfilePlayReportRecordInput {
pub owner_user_id: String,
pub profile_id: String,
pub played_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldProfileLikeReportRecordInput {
pub owner_user_id: String,
pub profile_id: String,
pub user_id: String,
pub liked_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldPublishWorldRecordInput {
pub session_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub public_work_code: Option<String>,
pub author_public_user_code: String,
pub draft_profile_json: String,
pub legacy_result_profile_json: Option<String>,
pub setting_text: String,
pub author_display_name: String,
pub published_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldAgentMessageSubmitRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub operation_id: String,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldAgentActionExecuteRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub operation_id: String,
pub action: String,
pub payload_json: Option<String>,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldAgentActionExecuteRecord {
pub operation: CustomWorldAgentOperationRecord,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishPlayReportRecordInput {
pub session_id: String,
pub user_id: String,
pub elapsed_ms: u64,
pub reported_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishRunStartRecordInput {
pub run_id: String,
pub session_id: String,
pub owner_user_id: String,
pub started_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishInputSubmitRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub x: f32,
pub y: f32,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishLikeReportRecordInput {
pub session_id: String,
pub user_id: String,
pub liked_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishWorkRemixRecordInput {
pub source_session_id: String,
pub target_session_id: String,
pub target_owner_user_id: String,
pub welcome_message_id: String,
pub remixed_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentSessionCreateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub welcome_message_id: String,
pub welcome_message_text: String,
pub config_json: Option<String>,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentMessageSubmitRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentMessageFinalizeRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub assistant_message_id: Option<String>,
pub assistant_reply_text: Option<String>,
pub config_json: Option<String>,
pub progress_percent: u32,
pub stage: String,
pub updated_at_micros: i64,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleCompileDraftRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub author_display_name: String,
pub game_name: Option<String>,
pub summary_text: Option<String>,
pub tags_json: Option<String>,
pub cover_image_src: Option<String>,
pub compiled_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleWorkUpdateRecordInput {
pub profile_id: String,
pub owner_user_id: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary_text: String,
pub tags_json: String,
pub cover_image_src: String,
pub background_prompt: String,
pub background_image_src: String,
pub shape_options_json: String,
pub hole_options_json: String,
pub shape_count: u32,
pub difficulty: u32,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunStartRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub started_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunDropRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub hole_id: String,
pub client_snapshot_version: u64,
pub client_event_id: String,
pub dropped_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunStopRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub stopped_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunRestartRecordInput {
pub source_run_id: String,
pub next_run_id: String,
pub owner_user_id: String,
pub restarted_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunTimeUpRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub finished_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelAgentMessageSubmitRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelAgentMessageFinalizeRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub assistant_message_id: Option<String>,
pub assistant_reply_text: Option<String>,
pub draft_json: Option<String>,
pub pending_action_json: Option<String>,
pub status: String,
pub progress_percent: u32,
pub updated_at_micros: i64,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelWorkCompileRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub work_id: Option<String>,
pub author_display_name: String,
pub work_title: Option<String>,
pub work_description: Option<String>,
pub tags_json: Option<String>,
pub cover_image_src: Option<String>,
pub compiled_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelRunStartRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub mode: String,
pub snapshot_json: Option<String>,
pub started_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelRunSnapshotRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub status: String,
pub current_scene_id: Option<String>,
pub current_phase_id: Option<String>,
pub visible_character_ids_json: String,
pub flags_json: String,
pub metrics_json: String,
pub available_choices_json: String,
pub text_mode_enabled: bool,
pub snapshot_json: Option<String>,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelHistoryEntryRecordInput {
pub entry_id: String,
pub run_id: String,
pub owner_user_id: String,
pub turn_index: u32,
pub source: String,
pub action_text: Option<String>,
pub steps_json: String,
pub snapshot_before_hash: Option<String>,
pub snapshot_after_hash: Option<String>,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VisualNovelAgentMessageRecord {
pub message_id: String,
pub session_id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VisualNovelHistoryEntryRecord {
pub entry_id: String,
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub turn_index: u32,
pub source: String,
pub action_text: Option<String>,
pub steps: serde_json::Value,
pub snapshot_before_hash: Option<String>,
pub snapshot_after_hash: Option<String>,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VisualNovelRunRecord {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub mode: String,
pub status: String,
pub current_scene_id: Option<String>,
pub current_phase_id: Option<String>,
pub visible_character_ids: Vec<String>,
pub flags: serde_json::Value,
pub metrics: serde_json::Value,
pub history: Vec<VisualNovelHistoryEntryRecord>,
pub available_choices: serde_json::Value,
pub text_mode_enabled: bool,
pub created_at: String,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAnchorItemRecord {
pub key: String,
pub label: String,
pub value: String,
pub status: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAnchorPackRecord {
pub theme: SquareHoleAnchorItemRecord,
pub twist_rule: SquareHoleAnchorItemRecord,
pub shape_count: SquareHoleAnchorItemRecord,
pub difficulty: SquareHoleAnchorItemRecord,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleCreatorConfigRecord {
pub theme_text: String,
pub twist_rule: String,
pub shape_count: u32,
pub difficulty: u32,
pub shape_options: Vec<SquareHoleShapeOptionRecord>,
pub hole_options: Vec<SquareHoleHoleOptionRecord>,
pub background_prompt: String,
pub cover_image_src: Option<String>,
pub background_image_src: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleShapeOptionRecord {
pub option_id: String,
pub shape_kind: String,
pub label: String,
pub target_hole_id: String,
pub image_prompt: String,
pub image_src: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleHoleOptionRecord {
pub hole_id: String,
pub hole_kind: String,
pub label: String,
pub image_prompt: String,
pub image_src: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleResultDraftRecord {
pub profile_id: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
pub cover_image_src: Option<String>,
pub background_prompt: String,
pub background_image_src: Option<String>,
pub shape_options: Vec<SquareHoleShapeOptionRecord>,
pub hole_options: Vec<SquareHoleHoleOptionRecord>,
pub shape_count: u32,
pub difficulty: u32,
pub publish_ready: bool,
pub blockers: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentMessageRecord {
pub id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentSessionRecord {
pub session_id: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: SquareHoleAnchorPackRecord,
pub config: SquareHoleCreatorConfigRecord,
pub draft: Option<SquareHoleResultDraftRecord>,
pub messages: Vec<SquareHoleAgentMessageRecord>,
pub last_assistant_reply: Option<String>,
pub published_profile_id: Option<String>,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleWorkProfileRecord {
pub work_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub source_session_id: Option<String>,
pub author_display_name: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
pub cover_image_src: Option<String>,
pub background_prompt: String,
pub background_image_src: Option<String>,
pub shape_options: Vec<SquareHoleShapeOptionRecord>,
pub hole_options: Vec<SquareHoleHoleOptionRecord>,
pub shape_count: u32,
pub difficulty: u32,
pub publication_status: String,
pub play_count: u32,
pub updated_at: String,
pub published_at: Option<String>,
pub publish_ready: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleShapeSnapshotRecord {
pub shape_id: String,
pub shape_kind: String,
pub label: String,
pub target_hole_id: String,
pub color: String,
pub image_src: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SquareHoleHoleSnapshotRecord {
pub hole_id: String,
pub hole_kind: String,
pub label: String,
pub x: f32,
pub y: f32,
pub image_src: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishSessionCreateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub welcome_message_id: String,
pub welcome_message_text: String,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishMessageSubmitRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub assistant_message_id: String,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishMessageFinalizeRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub assistant_message_id: Option<String>,
pub assistant_reply_text: Option<String>,
pub stage: String,
pub progress_percent: u32,
pub anchor_pack_json: String,
pub error_message: Option<String>,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishDraftCompileRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub draft_json: Option<String>,
pub compiled_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishAnchorItemRecord {
pub key: String,
pub label: String,
pub value: String,
pub status: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishAnchorPackRecord {
pub gameplay_promise: BigFishAnchorItemRecord,
pub ecology_visual_theme: BigFishAnchorItemRecord,
pub growth_ladder: BigFishAnchorItemRecord,
pub risk_tempo: BigFishAnchorItemRecord,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishLevelBlueprintRecord {
pub level: u32,
pub name: String,
pub one_line_fantasy: String,
pub text_description: String,
pub silhouette_direction: String,
pub size_ratio: f32,
pub visual_description: String,
pub visual_prompt_seed: String,
pub idle_motion_description: String,
pub move_motion_description: String,
pub motion_prompt_seed: String,
pub merge_source_level: Option<u32>,
pub prey_window: Vec<u32>,
pub threat_window: Vec<u32>,
pub is_final_level: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishBackgroundBlueprintRecord {
pub theme: String,
pub color_mood: String,
pub foreground_hints: String,
pub midground_composition: String,
pub background_depth: String,
pub safe_play_area_hint: String,
pub spawn_edge_hint: String,
pub background_prompt_seed: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BigFishAgentMessageRecord {
pub message_id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishVector2Record {
pub x: f32,
pub y: f32,
}

View File

@@ -0,0 +1,957 @@
use super::*;
impl From<CustomWorldProfileUpsertRecordInput> for CustomWorldProfileUpsertInput {
fn from(input: CustomWorldProfileUpsertRecordInput) -> Self {
Self {
profile_id: input.profile_id,
owner_user_id: input.owner_user_id,
public_work_code: input.public_work_code,
author_public_user_code: input.author_public_user_code,
source_agent_session_id: input.source_agent_session_id,
world_name: input.world_name,
subtitle: input.subtitle,
summary_text: input.summary_text,
theme_mode: map_custom_world_theme_mode(input.theme_mode),
cover_image_src: input.cover_image_src,
profile_payload_json: input.profile_payload_json,
playable_npc_count: input.playable_npc_count,
landmark_count: input.landmark_count,
author_display_name: input.author_display_name,
updated_at_micros: input.updated_at_micros,
}
}
}
pub(crate) fn map_custom_world_profile_list_result(
result: CustomWorldProfileListResult,
) -> Result<Vec<CustomWorldLibraryEntryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.entries
.into_iter()
.map(map_custom_world_library_entry_from_profile_snapshot)
.collect()
}
pub(crate) fn map_custom_world_library_detail_result(
result: CustomWorldLibraryMutationResult,
) -> Result<CustomWorldLibraryMutationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let entry = result
.entry
.ok_or_else(|| SpacetimeClientError::Procedure("custom_world_profile 不存在".to_string()))
.and_then(map_custom_world_library_entry_from_profile_snapshot)?;
let gallery_entry = result
.gallery_entry
.map(map_custom_world_gallery_entry_snapshot)
.transpose()?;
Ok(CustomWorldLibraryMutationRecord {
entry,
gallery_entry,
})
}
pub(crate) fn map_custom_world_gallery_list_result(
result: CustomWorldGalleryListResult,
) -> Result<Vec<CustomWorldGalleryEntryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.entries
.into_iter()
.map(map_custom_world_gallery_entry_snapshot)
.collect::<Result<Vec<_>, _>>()?)
}
pub(crate) fn map_custom_world_library_mutation_result(
result: CustomWorldLibraryMutationResult,
) -> Result<CustomWorldLibraryMutationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let entry = result
.entry
.ok_or_else(|| SpacetimeClientError::missing_snapshot("custom world entry"))
.and_then(map_custom_world_library_entry_from_profile_snapshot)?;
let gallery_entry = result
.gallery_entry
.map(map_custom_world_gallery_entry_snapshot)
.transpose()?;
Ok(CustomWorldLibraryMutationRecord {
entry,
gallery_entry,
})
}
pub(crate) fn map_custom_world_publish_world_result(
result: CustomWorldPublishWorldResult,
) -> Result<CustomWorldPublishWorldRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let compiled_record = result
.compiled_record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("published profile compile 快照"))
.and_then(map_custom_world_published_profile_compile_snapshot)?;
let entry = result
.entry
.ok_or_else(|| SpacetimeClientError::missing_snapshot("custom world entry"))
.and_then(map_custom_world_library_entry_from_profile_snapshot)?;
let gallery_entry = result
.gallery_entry
.map(map_custom_world_gallery_entry_snapshot)
.transpose()?;
let session_stage = result
.session_stage
.ok_or_else(|| SpacetimeClientError::missing_snapshot("session stage"))
.map(map_rpg_agent_stage)?;
Ok(CustomWorldPublishWorldRecord {
compiled_record,
entry,
gallery_entry,
session_stage,
})
}
pub(crate) fn map_custom_world_agent_session_procedure_result(
result: CustomWorldAgentSessionProcedureResult,
) -> Result<CustomWorldAgentSessionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("custom world agent session 快照"))?;
map_custom_world_agent_session_snapshot(session)
}
pub(crate) fn map_custom_world_agent_operation_procedure_result(
result: CustomWorldAgentOperationProcedureResult,
) -> Result<CustomWorldAgentOperationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let operation = result.operation.ok_or_else(|| {
SpacetimeClientError::missing_snapshot("custom world agent operation 快照")
})?;
Ok(map_custom_world_agent_operation_snapshot(operation))
}
pub(crate) fn map_custom_world_works_list_result(
result: CustomWorldWorksListResult,
) -> Result<Vec<CustomWorldWorkSummaryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.items
.into_iter()
.map(map_custom_world_work_summary_snapshot)
.collect()
}
pub(crate) fn map_custom_world_draft_card_detail_result(
result: CustomWorldDraftCardDetailResult,
) -> Result<CustomWorldDraftCardDetailRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let card = result
.card
.ok_or_else(|| SpacetimeClientError::missing_snapshot("custom world card detail 快照"))?;
map_custom_world_draft_card_detail_snapshot(card)
}
pub(crate) fn map_custom_world_agent_action_execute_result(
result: CustomWorldAgentActionExecuteResult,
) -> Result<CustomWorldAgentActionExecuteRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let operation = result.operation.ok_or_else(|| {
SpacetimeClientError::missing_snapshot("custom world action operation 快照")
})?;
Ok(CustomWorldAgentActionExecuteRecord {
operation: map_custom_world_agent_operation_snapshot(operation),
})
}
pub(crate) fn map_custom_world_library_entry_from_profile_snapshot(
snapshot: CustomWorldProfileSnapshot,
) -> Result<CustomWorldLibraryEntryRecord, SpacetimeClientError> {
let profile = serde_json::from_str::<serde_json::Value>(&snapshot.profile_payload_json)
.map_err(|error| {
SpacetimeClientError::Runtime(format!(
"custom world profile payload JSON 非法: {error}"
))
})?;
Ok(CustomWorldLibraryEntryRecord {
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
public_work_code: snapshot.public_work_code,
author_public_user_code: snapshot.author_public_user_code,
profile,
visibility: map_custom_world_publication_status(snapshot.publication_status).to_string(),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
author_display_name: snapshot.author_display_name,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
cover_image_src: snapshot.cover_image_src,
theme_mode: format_custom_world_theme_mode(map_custom_world_theme_mode_back(
snapshot.theme_mode,
))
.to_string(),
playable_npc_count: snapshot.playable_npc_count,
landmark_count: snapshot.landmark_count,
play_count: snapshot.play_count,
remix_count: snapshot.remix_count,
like_count: snapshot.like_count,
recent_play_count_7d: 0,
})
}
pub(crate) fn map_custom_world_gallery_entry_snapshot(
snapshot: CustomWorldGalleryEntrySnapshot,
) -> Result<CustomWorldGalleryEntryRecord, SpacetimeClientError> {
Ok(CustomWorldGalleryEntryRecord {
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
public_work_code: snapshot.public_work_code,
author_public_user_code: snapshot.author_public_user_code,
visibility: "published".to_string(),
published_at: Some(format_timestamp_micros(snapshot.published_at_micros)),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
author_display_name: snapshot.author_display_name,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
cover_image_src: snapshot.cover_image_src,
theme_mode: format_custom_world_theme_mode(map_custom_world_theme_mode_back(
snapshot.theme_mode,
))
.to_string(),
playable_npc_count: snapshot.playable_npc_count,
landmark_count: snapshot.landmark_count,
play_count: snapshot.play_count,
remix_count: snapshot.remix_count,
like_count: snapshot.like_count,
recent_play_count_7d: snapshot.recent_play_count_7_d,
})
}
pub(crate) fn map_custom_world_gallery_entry_row(
row: CustomWorldGalleryEntry,
recent_play_count_7d: u32,
) -> CustomWorldGalleryEntryRecord {
CustomWorldGalleryEntryRecord {
owner_user_id: row.owner_user_id,
profile_id: row.profile_id,
public_work_code: row.public_work_code,
author_public_user_code: row.author_public_user_code,
visibility: "published".to_string(),
published_at: Some(format_timestamp_micros(
row.published_at.to_micros_since_unix_epoch(),
)),
updated_at: format_timestamp_micros(row.updated_at.to_micros_since_unix_epoch()),
author_display_name: row.author_display_name,
world_name: row.world_name,
subtitle: row.subtitle,
summary_text: row.summary_text,
cover_image_src: row.cover_image_src,
theme_mode: format_custom_world_theme_mode(map_custom_world_theme_mode_back(
row.theme_mode,
))
.to_string(),
playable_npc_count: row.playable_npc_count,
landmark_count: row.landmark_count,
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count,
recent_play_count_7d,
}
}
pub(crate) fn map_custom_world_published_profile_compile_snapshot(
snapshot: CustomWorldPublishedProfileCompileSnapshot,
) -> Result<CustomWorldPublishedProfileCompileRecord, SpacetimeClientError> {
let compiled_profile =
serde_json::from_str::<serde_json::Value>(&snapshot.compiled_profile_payload_json)
.map_err(|error| {
SpacetimeClientError::Runtime(format!(
"published profile compile JSON 非法: {error}"
))
})?;
Ok(CustomWorldPublishedProfileCompileRecord {
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
theme_mode: format_custom_world_theme_mode(map_custom_world_theme_mode_back(
snapshot.theme_mode,
))
.to_string(),
cover_image_src: snapshot.cover_image_src,
playable_npc_count: snapshot.playable_npc_count,
landmark_count: snapshot.landmark_count,
author_display_name: snapshot.author_display_name,
compiled_profile: compiled_profile,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
})
}
pub(crate) fn map_custom_world_work_summary_snapshot(
snapshot: CustomWorldWorkSummarySnapshot,
) -> Result<CustomWorldWorkSummaryRecord, SpacetimeClientError> {
Ok(CustomWorldWorkSummaryRecord {
work_id: snapshot.work_id,
source_type: snapshot.source_type,
status: snapshot.status,
title: snapshot.title,
subtitle: snapshot.subtitle,
summary: snapshot.summary,
cover_image_src: snapshot.cover_image_src,
cover_render_mode: snapshot.cover_render_mode,
cover_character_image_srcs: parse_json_string_array(
&snapshot.cover_character_image_srcs_json,
"custom world work cover_character_image_srcs_json",
)?,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
stage: snapshot.stage.map(map_rpg_agent_stage),
stage_label: snapshot.stage_label,
playable_npc_count: snapshot.playable_npc_count,
landmark_count: snapshot.landmark_count,
role_visual_ready_count: snapshot.role_visual_ready_count,
role_animation_ready_count: snapshot.role_animation_ready_count,
role_asset_summary_label: snapshot.role_asset_summary_label,
session_id: snapshot.session_id,
profile_id: snapshot.profile_id,
can_resume: snapshot.can_resume,
can_enter_world: snapshot.can_enter_world,
blocker_count: snapshot.blocker_count,
publish_ready: snapshot.publish_ready,
})
}
pub(crate) fn map_custom_world_agent_session_snapshot(
snapshot: CustomWorldAgentSessionSnapshot,
) -> Result<CustomWorldAgentSessionRecord, SpacetimeClientError> {
let anchor_content = parse_json_value(
&snapshot.anchor_content_json,
"custom world agent anchor_content_json",
)?;
let creator_intent = parse_optional_json_value(
snapshot.creator_intent_json.as_deref(),
serde_json::json!({}),
"custom world agent creator_intent_json",
)?;
let creator_intent_readiness = parse_json_value(
&snapshot.creator_intent_readiness_json,
"custom world agent creator_intent_readiness_json",
)?;
let anchor_pack = parse_optional_json_value(
snapshot.anchor_pack_json.as_deref(),
serde_json::json!({}),
"custom world agent anchor_pack_json",
)?;
let lock_state = parse_optional_json_value(
snapshot.lock_state_json.as_deref(),
serde_json::json!({}),
"custom world agent lock_state_json",
)?;
let draft_profile = parse_optional_json_value(
snapshot.draft_profile_json.as_deref(),
serde_json::json!({}),
"custom world agent draft_profile_json",
)?;
let pending_clarifications = parse_json_array(
&snapshot.pending_clarifications_json,
"custom world agent pending_clarifications_json",
)?;
let suggested_actions = parse_json_array(
&snapshot.suggested_actions_json,
"custom world agent suggested_actions_json",
)?;
let recommended_replies = parse_json_string_array(
&snapshot.recommended_replies_json,
"custom world agent recommended_replies_json",
)?;
let quality_findings = parse_json_array(
&snapshot.quality_findings_json,
"custom world agent quality_findings_json",
)?;
let asset_coverage = parse_json_value(
&snapshot.asset_coverage_json,
"custom world agent asset_coverage_json",
)?;
let checkpoints_json = parse_json_array(
&snapshot.checkpoints_json,
"custom world agent checkpoints_json",
)?;
let checkpoints = checkpoints_json
.into_iter()
.map(map_custom_world_checkpoint_record)
.collect::<Result<Vec<_>, _>>()?;
let supported_actions = parse_supported_actions_json(&snapshot.supported_actions_json)?;
let publish_gate = snapshot
.publish_gate_json
.as_deref()
.map(parse_custom_world_publish_gate_record)
.transpose()?;
Ok(CustomWorldAgentSessionRecord {
session_id: snapshot.session_id,
seed_text: snapshot.seed_text,
current_turn: snapshot.current_turn,
anchor_content,
progress_percent: snapshot.progress_percent,
last_assistant_reply: snapshot.last_assistant_reply,
stage: map_rpg_agent_stage(snapshot.stage),
focus_card_id: snapshot.focus_card_id,
creator_intent,
creator_intent_readiness,
anchor_pack,
lock_state,
draft_profile,
messages: snapshot
.messages
.into_iter()
.map(map_custom_world_agent_message_snapshot)
.collect(),
draft_cards: snapshot
.draft_cards
.into_iter()
.map(map_custom_world_draft_card_snapshot)
.collect::<Result<Vec<_>, _>>()?,
pending_clarifications,
suggested_actions,
recommended_replies,
quality_findings,
asset_coverage,
checkpoints,
supported_actions,
publish_gate,
result_preview: snapshot
.result_preview_json
.as_deref()
.map(|value| parse_json_value(value, "custom world agent result_preview_json"))
.transpose()?,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
})
}
pub(crate) fn map_custom_world_agent_message_snapshot(
snapshot: CustomWorldAgentMessageSnapshot,
) -> CustomWorldAgentMessageRecord {
CustomWorldAgentMessageRecord {
message_id: snapshot.message_id,
role: format_rpg_agent_message_role(snapshot.role).to_string(),
kind: format_rpg_agent_message_kind(snapshot.kind).to_string(),
text: snapshot.text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
related_operation_id: snapshot.related_operation_id,
}
}
pub(crate) fn map_custom_world_agent_operation_snapshot(
snapshot: CustomWorldAgentOperationSnapshot,
) -> CustomWorldAgentOperationRecord {
CustomWorldAgentOperationRecord {
operation_id: snapshot.operation_id,
operation_type: format_rpg_agent_operation_type(snapshot.operation_type).to_string(),
status: format_rpg_agent_operation_status(snapshot.status).to_string(),
phase_label: snapshot.phase_label,
phase_detail: snapshot.phase_detail,
progress: snapshot.progress,
error_message: snapshot.error_message,
started_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_custom_world_draft_card_snapshot(
snapshot: CustomWorldDraftCardSnapshot,
) -> Result<CustomWorldDraftCardRecord, SpacetimeClientError> {
Ok(CustomWorldDraftCardRecord {
card_id: snapshot.card_id,
kind: format_rpg_agent_draft_card_kind(snapshot.kind).to_string(),
title: snapshot.title,
subtitle: snapshot.subtitle,
summary: snapshot.summary,
status: format_rpg_agent_draft_card_status(snapshot.status).to_string(),
linked_ids: parse_json_string_array(
&snapshot.linked_ids_json,
"custom world draft_card linked_ids_json",
)?,
warning_count: snapshot.warning_count,
asset_status: snapshot
.asset_status
.map(format_custom_world_role_asset_status_back),
asset_status_label: snapshot.asset_status_label,
detail_payload: snapshot
.detail_payload_json
.as_deref()
.map(|value| parse_json_value(value, "custom world draft_card detail_payload_json"))
.transpose()?,
})
}
pub(crate) fn map_custom_world_draft_card_detail_snapshot(
snapshot: CustomWorldDraftCardDetailSnapshot,
) -> Result<CustomWorldDraftCardDetailRecord, SpacetimeClientError> {
Ok(CustomWorldDraftCardDetailRecord {
card_id: snapshot.card_id,
kind: format_rpg_agent_draft_card_kind(snapshot.kind).to_string(),
title: snapshot.title,
sections: snapshot
.sections
.into_iter()
.map(map_custom_world_draft_card_detail_section_snapshot)
.collect(),
linked_ids: parse_json_string_array(
&snapshot.linked_ids_json,
"custom world card detail linked_ids_json",
)?,
locked: snapshot.locked,
editable: snapshot.editable,
editable_section_ids: parse_json_string_array(
&snapshot.editable_section_ids_json,
"custom world card detail editable_section_ids_json",
)?,
warning_messages: parse_json_string_array(
&snapshot.warning_messages_json,
"custom world card detail warning_messages_json",
)?,
asset_status: snapshot
.asset_status
.map(format_custom_world_role_asset_status_back),
asset_status_label: snapshot.asset_status_label,
})
}
pub(crate) fn map_custom_world_draft_card_detail_section_snapshot(
snapshot: CustomWorldDraftCardDetailSectionSnapshot,
) -> CustomWorldDraftCardDetailSectionRecord {
CustomWorldDraftCardDetailSectionRecord {
section_id: snapshot.section_id,
label: snapshot.label,
value: snapshot.value,
}
}
pub(crate) fn map_custom_world_theme_mode(
value: DomainCustomWorldThemeMode,
) -> CustomWorldThemeMode {
match value {
DomainCustomWorldThemeMode::Martial => CustomWorldThemeMode::Martial,
DomainCustomWorldThemeMode::Arcane => CustomWorldThemeMode::Arcane,
DomainCustomWorldThemeMode::Machina => CustomWorldThemeMode::Machina,
DomainCustomWorldThemeMode::Tide => CustomWorldThemeMode::Tide,
DomainCustomWorldThemeMode::Rift => CustomWorldThemeMode::Rift,
DomainCustomWorldThemeMode::Mythic => CustomWorldThemeMode::Mythic,
}
}
pub(crate) fn map_custom_world_theme_mode_back(
value: CustomWorldThemeMode,
) -> DomainCustomWorldThemeMode {
match value {
CustomWorldThemeMode::Martial => DomainCustomWorldThemeMode::Martial,
CustomWorldThemeMode::Arcane => DomainCustomWorldThemeMode::Arcane,
CustomWorldThemeMode::Machina => DomainCustomWorldThemeMode::Machina,
CustomWorldThemeMode::Tide => DomainCustomWorldThemeMode::Tide,
CustomWorldThemeMode::Rift => DomainCustomWorldThemeMode::Rift,
CustomWorldThemeMode::Mythic => DomainCustomWorldThemeMode::Mythic,
}
}
pub(crate) fn map_custom_world_publication_status(
value: CustomWorldPublicationStatus,
) -> &'static str {
match value {
CustomWorldPublicationStatus::Draft => "draft",
CustomWorldPublicationStatus::Published => "published",
}
}
pub(crate) fn map_rpg_agent_stage(value: crate::module_bindings::RpgAgentStage) -> String {
match value {
crate::module_bindings::RpgAgentStage::CollectingIntent => "collecting_intent",
crate::module_bindings::RpgAgentStage::Clarifying => "clarifying",
crate::module_bindings::RpgAgentStage::FoundationReview => "foundation_review",
crate::module_bindings::RpgAgentStage::ObjectRefining => "object_refining",
crate::module_bindings::RpgAgentStage::VisualRefining => "visual_refining",
crate::module_bindings::RpgAgentStage::LongTailReview => "long_tail_review",
crate::module_bindings::RpgAgentStage::ReadyToPublish => "ready_to_publish",
crate::module_bindings::RpgAgentStage::Published => "published",
crate::module_bindings::RpgAgentStage::Error => "error",
}
.to_string()
}
pub(crate) fn parse_rpg_agent_stage_record(
value: &str,
) -> Result<crate::module_bindings::RpgAgentStage, SpacetimeClientError> {
match value.trim() {
"collecting_intent" => Ok(crate::module_bindings::RpgAgentStage::CollectingIntent),
"clarifying" => Ok(crate::module_bindings::RpgAgentStage::Clarifying),
"foundation_review" => Ok(crate::module_bindings::RpgAgentStage::FoundationReview),
"object_refining" => Ok(crate::module_bindings::RpgAgentStage::ObjectRefining),
"visual_refining" => Ok(crate::module_bindings::RpgAgentStage::VisualRefining),
"long_tail_review" => Ok(crate::module_bindings::RpgAgentStage::LongTailReview),
"ready_to_publish" => Ok(crate::module_bindings::RpgAgentStage::ReadyToPublish),
"published" => Ok(crate::module_bindings::RpgAgentStage::Published),
"error" => Ok(crate::module_bindings::RpgAgentStage::Error),
other => Err(SpacetimeClientError::Runtime(format!(
"未知 rpg agent stage: {other}"
))),
}
}
pub(crate) fn format_rpg_agent_message_role(
value: crate::module_bindings::RpgAgentMessageRole,
) -> &'static str {
match value {
crate::module_bindings::RpgAgentMessageRole::User => "user",
crate::module_bindings::RpgAgentMessageRole::Assistant => "assistant",
crate::module_bindings::RpgAgentMessageRole::System => "system",
}
}
pub(crate) fn format_rpg_agent_message_kind(
value: crate::module_bindings::RpgAgentMessageKind,
) -> &'static str {
match value {
crate::module_bindings::RpgAgentMessageKind::Chat => "chat",
crate::module_bindings::RpgAgentMessageKind::Clarification => "clarification",
crate::module_bindings::RpgAgentMessageKind::Summary => "summary",
crate::module_bindings::RpgAgentMessageKind::Checkpoint => "checkpoint",
crate::module_bindings::RpgAgentMessageKind::Warning => "warning",
crate::module_bindings::RpgAgentMessageKind::ActionResult => "action_result",
}
}
pub(crate) fn format_rpg_agent_operation_type(
value: crate::module_bindings::RpgAgentOperationType,
) -> &'static str {
match value {
crate::module_bindings::RpgAgentOperationType::ProcessMessage => "process_message",
crate::module_bindings::RpgAgentOperationType::DraftFoundation => "draft_foundation",
crate::module_bindings::RpgAgentOperationType::UpdateDraftCard => "update_draft_card",
crate::module_bindings::RpgAgentOperationType::SyncResultProfile => "sync_result_profile",
crate::module_bindings::RpgAgentOperationType::GenerateCharacters => "generate_characters",
crate::module_bindings::RpgAgentOperationType::GenerateLandmarks => "generate_landmarks",
crate::module_bindings::RpgAgentOperationType::GenerateRoleAssets => "generate_role_assets",
crate::module_bindings::RpgAgentOperationType::SyncRoleAssets => "sync_role_assets",
crate::module_bindings::RpgAgentOperationType::GenerateSceneAssets => {
"generate_scene_assets"
}
crate::module_bindings::RpgAgentOperationType::SyncSceneAssets => "sync_scene_assets",
crate::module_bindings::RpgAgentOperationType::ExpandLongTail => "expand_long_tail",
crate::module_bindings::RpgAgentOperationType::PublishWorld => "publish_world",
crate::module_bindings::RpgAgentOperationType::RevertCheckpoint => "revert_checkpoint",
crate::module_bindings::RpgAgentOperationType::DeleteCharacters => "delete_characters",
crate::module_bindings::RpgAgentOperationType::DeleteLandmarks => "delete_landmarks",
}
}
pub(crate) fn parse_rpg_agent_operation_type_record(
value: &str,
) -> Result<crate::module_bindings::RpgAgentOperationType, SpacetimeClientError> {
match value.trim() {
"process_message" => Ok(crate::module_bindings::RpgAgentOperationType::ProcessMessage),
"draft_foundation" => Ok(crate::module_bindings::RpgAgentOperationType::DraftFoundation),
"update_draft_card" => Ok(crate::module_bindings::RpgAgentOperationType::UpdateDraftCard),
"sync_result_profile" => {
Ok(crate::module_bindings::RpgAgentOperationType::SyncResultProfile)
}
"generate_characters" => {
Ok(crate::module_bindings::RpgAgentOperationType::GenerateCharacters)
}
"generate_landmarks" => {
Ok(crate::module_bindings::RpgAgentOperationType::GenerateLandmarks)
}
"generate_role_assets" => {
Ok(crate::module_bindings::RpgAgentOperationType::GenerateRoleAssets)
}
"sync_role_assets" => Ok(crate::module_bindings::RpgAgentOperationType::SyncRoleAssets),
"generate_scene_assets" => {
Ok(crate::module_bindings::RpgAgentOperationType::GenerateSceneAssets)
}
"sync_scene_assets" => Ok(crate::module_bindings::RpgAgentOperationType::SyncSceneAssets),
"expand_long_tail" => Ok(crate::module_bindings::RpgAgentOperationType::ExpandLongTail),
"publish_world" => Ok(crate::module_bindings::RpgAgentOperationType::PublishWorld),
"revert_checkpoint" => Ok(crate::module_bindings::RpgAgentOperationType::RevertCheckpoint),
"delete_characters" => Ok(crate::module_bindings::RpgAgentOperationType::DeleteCharacters),
"delete_landmarks" => Ok(crate::module_bindings::RpgAgentOperationType::DeleteLandmarks),
other => Err(SpacetimeClientError::Runtime(format!(
"未知 rpg agent operation type: {other}"
))),
}
}
pub(crate) fn format_rpg_agent_operation_status(
value: crate::module_bindings::RpgAgentOperationStatus,
) -> &'static str {
match value {
crate::module_bindings::RpgAgentOperationStatus::Queued => "queued",
crate::module_bindings::RpgAgentOperationStatus::Running => "running",
crate::module_bindings::RpgAgentOperationStatus::Completed => "completed",
crate::module_bindings::RpgAgentOperationStatus::Failed => "failed",
}
}
pub(crate) fn parse_rpg_agent_operation_status_record(
value: &str,
) -> Result<crate::module_bindings::RpgAgentOperationStatus, SpacetimeClientError> {
match value.trim() {
"queued" => Ok(crate::module_bindings::RpgAgentOperationStatus::Queued),
"running" => Ok(crate::module_bindings::RpgAgentOperationStatus::Running),
"completed" => Ok(crate::module_bindings::RpgAgentOperationStatus::Completed),
"failed" => Ok(crate::module_bindings::RpgAgentOperationStatus::Failed),
other => Err(SpacetimeClientError::Runtime(format!(
"未知 rpg agent operation status: {other}"
))),
}
}
pub(crate) fn format_rpg_agent_draft_card_kind(
value: crate::module_bindings::RpgAgentDraftCardKind,
) -> &'static str {
match value {
crate::module_bindings::RpgAgentDraftCardKind::World => "world",
crate::module_bindings::RpgAgentDraftCardKind::Camp => "camp",
crate::module_bindings::RpgAgentDraftCardKind::Faction => "faction",
crate::module_bindings::RpgAgentDraftCardKind::Character => "character",
crate::module_bindings::RpgAgentDraftCardKind::Landmark => "landmark",
crate::module_bindings::RpgAgentDraftCardKind::Thread => "thread",
crate::module_bindings::RpgAgentDraftCardKind::Chapter => "chapter",
crate::module_bindings::RpgAgentDraftCardKind::SceneChapter => "scene_chapter",
crate::module_bindings::RpgAgentDraftCardKind::Carrier => "carrier",
crate::module_bindings::RpgAgentDraftCardKind::SidequestSeed => "sidequest_seed",
}
}
pub(crate) fn format_rpg_agent_draft_card_status(
value: crate::module_bindings::RpgAgentDraftCardStatus,
) -> &'static str {
match value {
crate::module_bindings::RpgAgentDraftCardStatus::Suggested => "suggested",
crate::module_bindings::RpgAgentDraftCardStatus::Confirmed => "confirmed",
crate::module_bindings::RpgAgentDraftCardStatus::Locked => "locked",
crate::module_bindings::RpgAgentDraftCardStatus::Warning => "warning",
}
}
pub(crate) fn format_custom_world_role_asset_status_back(
value: crate::module_bindings::CustomWorldRoleAssetStatus,
) -> String {
match value {
crate::module_bindings::CustomWorldRoleAssetStatus::Missing => "missing",
crate::module_bindings::CustomWorldRoleAssetStatus::VisualReady => "visual_ready",
crate::module_bindings::CustomWorldRoleAssetStatus::AnimationsReady => "animations_ready",
crate::module_bindings::CustomWorldRoleAssetStatus::Complete => "complete",
}
.to_string()
}
pub(crate) fn format_custom_world_theme_mode(value: DomainCustomWorldThemeMode) -> &'static str {
match value {
DomainCustomWorldThemeMode::Martial => "martial",
DomainCustomWorldThemeMode::Arcane => "arcane",
DomainCustomWorldThemeMode::Machina => "machina",
DomainCustomWorldThemeMode::Tide => "tide",
DomainCustomWorldThemeMode::Rift => "rift",
DomainCustomWorldThemeMode::Mythic => "mythic",
}
}
pub(crate) fn format_ai_task_kind(value: AiTaskKind) -> &'static str {
match value {
AiTaskKind::StoryGeneration => "story_generation",
AiTaskKind::CharacterChat => "character_chat",
AiTaskKind::NpcChat => "npc_chat",
AiTaskKind::CustomWorldGeneration => "custom_world_generation",
AiTaskKind::QuestIntent => "quest_intent",
AiTaskKind::RuntimeItemIntent => "runtime_item_intent",
}
}
pub(crate) fn format_ai_result_reference_kind(value: AiResultReferenceKind) -> &'static str {
match value {
AiResultReferenceKind::StorySession => "story_session",
AiResultReferenceKind::StoryEvent => "story_event",
AiResultReferenceKind::CustomWorldProfile => "custom_world_profile",
AiResultReferenceKind::QuestRecord => "quest_record",
AiResultReferenceKind::RuntimeItemRecord => "runtime_item_record",
AiResultReferenceKind::AssetObject => "asset_object",
}
}
pub(crate) fn map_custom_world_checkpoint_record(
value: serde_json::Value,
) -> Result<CustomWorldCheckpointRecord, SpacetimeClientError> {
let object = value.as_object().ok_or_else(|| {
SpacetimeClientError::Runtime("custom world checkpoint 必须是 JSON object".to_string())
})?;
let checkpoint_id = object
.get("checkpointId")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world checkpoint.checkpointId 缺失".to_string())
})?;
let created_at = object
.get("createdAt")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world checkpoint.createdAt 缺失".to_string())
})?;
let label = object
.get("label")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world checkpoint.label 缺失".to_string())
})?;
Ok(CustomWorldCheckpointRecord {
checkpoint_id: checkpoint_id.to_string(),
created_at: created_at.to_string(),
label: label.to_string(),
})
}
pub(crate) fn parse_custom_world_publish_gate_record(
value: &str,
) -> Result<CustomWorldPublishGateRecord, SpacetimeClientError> {
let object = parse_json_value(value, "custom world publish_gate_json")?
.as_object()
.cloned()
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world publish_gate_json 必须是 JSON object".to_string(),
)
})?;
let profile_id = object
.get("profileId")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world publish_gate.profileId 缺失".to_string())
})?;
let blockers = object
.get("blockers")
.and_then(serde_json::Value::as_array)
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world publish_gate.blockers 缺失".to_string())
})?
.iter()
.cloned()
.map(|entry| {
let object = entry.as_object().ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world publish gate blocker 必须是 JSON object".to_string(),
)
})?;
let id = object
.get("id")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world publish gate blocker.id 缺失".to_string(),
)
})?;
let code = object
.get("code")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world publish gate blocker.code 缺失".to_string(),
)
})?;
let message = object
.get("message")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world publish gate blocker.message 缺失".to_string(),
)
})?;
Ok(CustomWorldResultPreviewBlockerRecord {
id: id.to_string(),
code: code.to_string(),
message: message.to_string(),
})
})
.collect::<Result<Vec<_>, _>>()?;
let blocker_count = object
.get("blockerCount")
.and_then(serde_json::Value::as_u64)
.and_then(|value| u32::try_from(value).ok())
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world publish_gate.blockerCount 缺失".to_string())
})?;
let publish_ready = object
.get("publishReady")
.and_then(serde_json::Value::as_bool)
.ok_or_else(|| {
SpacetimeClientError::Runtime("custom world publish_gate.publishReady 缺失".to_string())
})?;
let can_enter_world = object
.get("canEnterWorld")
.and_then(serde_json::Value::as_bool)
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world publish_gate.canEnterWorld 缺失".to_string(),
)
})?;
Ok(CustomWorldPublishGateRecord {
profile_id: profile_id.to_string(),
blockers,
blocker_count,
publish_ready,
can_enter_world,
})
}

View File

@@ -0,0 +1,200 @@
use super::*;
impl From<DomainRuntimeInventoryStateQueryInput> for RuntimeInventoryStateQueryInput {
fn from(input: DomainRuntimeInventoryStateQueryInput) -> Self {
Self {
runtime_session_id: input.runtime_session_id,
actor_user_id: input.actor_user_id,
}
}
}
pub(crate) fn map_runtime_inventory_state_procedure_result(
result: RuntimeInventoryStateProcedureResult,
) -> Result<RuntimeInventoryStateRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let snapshot = result
.snapshot
.ok_or_else(|| SpacetimeClientError::missing_snapshot("runtime inventory state 快照"))?;
Ok(build_runtime_inventory_state_record(
map_runtime_inventory_state_snapshot(snapshot),
))
}
pub(crate) fn map_runtime_inventory_state_snapshot(
snapshot: RuntimeInventoryStateSnapshot,
) -> DomainRuntimeInventoryStateSnapshot {
DomainRuntimeInventoryStateSnapshot {
runtime_session_id: snapshot.runtime_session_id,
actor_user_id: snapshot.actor_user_id,
backpack_items: snapshot
.backpack_items
.into_iter()
.map(map_inventory_slot_snapshot)
.collect(),
equipment_items: snapshot
.equipment_items
.into_iter()
.map(map_inventory_slot_snapshot)
.collect(),
}
}
pub(crate) fn map_inventory_slot_snapshot(
snapshot: InventorySlotSnapshot,
) -> module_inventory::InventorySlotSnapshot {
module_inventory::InventorySlotSnapshot {
slot_id: snapshot.slot_id,
runtime_session_id: snapshot.runtime_session_id,
story_session_id: snapshot.story_session_id,
actor_user_id: snapshot.actor_user_id,
container_kind: map_inventory_container_kind(snapshot.container_kind),
slot_key: snapshot.slot_key,
item_id: snapshot.item_id,
category: snapshot.category,
name: snapshot.name,
description: snapshot.description,
quantity: snapshot.quantity,
rarity: map_inventory_item_rarity(snapshot.rarity),
tags: snapshot.tags,
stackable: snapshot.stackable,
stack_key: snapshot.stack_key,
equipment_slot_id: snapshot.equipment_slot_id.map(map_inventory_equipment_slot),
source_kind: map_inventory_item_source_kind(snapshot.source_kind),
source_reference_id: snapshot.source_reference_id,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_runtime_item_reward_item_rarity(
value: DomainRuntimeItemRewardItemRarity,
) -> RuntimeItemRewardItemRarity {
match value {
DomainRuntimeItemRewardItemRarity::Common => RuntimeItemRewardItemRarity::Common,
DomainRuntimeItemRewardItemRarity::Uncommon => RuntimeItemRewardItemRarity::Uncommon,
DomainRuntimeItemRewardItemRarity::Rare => RuntimeItemRewardItemRarity::Rare,
DomainRuntimeItemRewardItemRarity::Epic => RuntimeItemRewardItemRarity::Epic,
DomainRuntimeItemRewardItemRarity::Legendary => RuntimeItemRewardItemRarity::Legendary,
}
}
pub(crate) fn map_runtime_item_equipment_slot(
value: DomainRuntimeItemEquipmentSlot,
) -> RuntimeItemEquipmentSlot {
match value {
DomainRuntimeItemEquipmentSlot::Weapon => RuntimeItemEquipmentSlot::Weapon,
DomainRuntimeItemEquipmentSlot::Armor => RuntimeItemEquipmentSlot::Armor,
DomainRuntimeItemEquipmentSlot::Relic => RuntimeItemEquipmentSlot::Relic,
}
}
pub(crate) fn map_runtime_item_reward_item_rarity_back(
value: RuntimeItemRewardItemRarity,
) -> DomainRuntimeItemRewardItemRarity {
match value {
RuntimeItemRewardItemRarity::Common => DomainRuntimeItemRewardItemRarity::Common,
RuntimeItemRewardItemRarity::Uncommon => DomainRuntimeItemRewardItemRarity::Uncommon,
RuntimeItemRewardItemRarity::Rare => DomainRuntimeItemRewardItemRarity::Rare,
RuntimeItemRewardItemRarity::Epic => DomainRuntimeItemRewardItemRarity::Epic,
RuntimeItemRewardItemRarity::Legendary => DomainRuntimeItemRewardItemRarity::Legendary,
}
}
pub(crate) fn map_runtime_item_equipment_slot_back(
value: RuntimeItemEquipmentSlot,
) -> DomainRuntimeItemEquipmentSlot {
match value {
RuntimeItemEquipmentSlot::Weapon => DomainRuntimeItemEquipmentSlot::Weapon,
RuntimeItemEquipmentSlot::Armor => DomainRuntimeItemEquipmentSlot::Armor,
RuntimeItemEquipmentSlot::Relic => DomainRuntimeItemEquipmentSlot::Relic,
}
}
pub(crate) fn map_ai_result_reference_kind(
value: DomainAiResultReferenceKind,
) -> AiResultReferenceKind {
match value {
DomainAiResultReferenceKind::StorySession => AiResultReferenceKind::StorySession,
DomainAiResultReferenceKind::StoryEvent => AiResultReferenceKind::StoryEvent,
DomainAiResultReferenceKind::CustomWorldProfile => {
AiResultReferenceKind::CustomWorldProfile
}
DomainAiResultReferenceKind::QuestRecord => AiResultReferenceKind::QuestRecord,
DomainAiResultReferenceKind::RuntimeItemRecord => AiResultReferenceKind::RuntimeItemRecord,
DomainAiResultReferenceKind::AssetObject => AiResultReferenceKind::AssetObject,
}
}
pub(crate) fn map_runtime_item_reward_item_snapshot(
snapshot: DomainRuntimeItemRewardItemSnapshot,
) -> RuntimeItemRewardItemSnapshot {
RuntimeItemRewardItemSnapshot {
item_id: snapshot.item_id,
category: snapshot.category,
item_name: snapshot.item_name,
description: snapshot.description,
quantity: snapshot.quantity,
rarity: map_runtime_item_reward_item_rarity(snapshot.rarity),
tags: snapshot.tags,
stackable: snapshot.stackable,
stack_key: snapshot.stack_key,
equipment_slot_id: snapshot
.equipment_slot_id
.map(map_runtime_item_equipment_slot),
}
}
pub(crate) fn map_runtime_item_reward_item_snapshot_back(
snapshot: RuntimeItemRewardItemSnapshot,
) -> DomainRuntimeItemRewardItemSnapshot {
DomainRuntimeItemRewardItemSnapshot {
item_id: snapshot.item_id,
category: snapshot.category,
item_name: snapshot.item_name,
description: snapshot.description,
quantity: snapshot.quantity,
rarity: map_runtime_item_reward_item_rarity_back(snapshot.rarity),
tags: snapshot.tags,
stackable: snapshot.stackable,
stack_key: snapshot.stack_key,
equipment_slot_id: snapshot
.equipment_slot_id
.map(map_runtime_item_equipment_slot_back),
}
}
pub(crate) fn map_inventory_container_kind(
value: InventoryContainerKind,
) -> module_inventory::InventoryContainerKind {
match value {
InventoryContainerKind::Backpack => module_inventory::InventoryContainerKind::Backpack,
InventoryContainerKind::Equipment => module_inventory::InventoryContainerKind::Equipment,
}
}
pub(crate) fn map_inventory_item_rarity(
value: InventoryItemRarity,
) -> module_inventory::InventoryItemRarity {
match value {
InventoryItemRarity::Common => module_inventory::InventoryItemRarity::Common,
InventoryItemRarity::Uncommon => module_inventory::InventoryItemRarity::Uncommon,
InventoryItemRarity::Rare => module_inventory::InventoryItemRarity::Rare,
InventoryItemRarity::Epic => module_inventory::InventoryItemRarity::Epic,
InventoryItemRarity::Legendary => module_inventory::InventoryItemRarity::Legendary,
}
}
pub(crate) fn map_inventory_equipment_slot(
value: InventoryEquipmentSlot,
) -> module_inventory::InventoryEquipmentSlot {
match value {
InventoryEquipmentSlot::Weapon => module_inventory::InventoryEquipmentSlot::Weapon,
InventoryEquipmentSlot::Armor => module_inventory::InventoryEquipmentSlot::Armor,
InventoryEquipmentSlot::Relic => module_inventory::InventoryEquipmentSlot::Relic,
}
}

View File

@@ -0,0 +1,606 @@
use super::*;
pub(crate) fn map_match3d_agent_session_procedure_result(
result: Match3DAgentSessionProcedureResult,
) -> Result<Match3DAgentSessionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
let session = result.session.ok_or_else(|| {
SpacetimeClientError::Procedure(
"SpacetimeDB procedure 未返回 match3d agent session 快照".to_string(),
)
})?;
Ok(map_match3d_agent_session_snapshot(session))
}
pub(crate) fn map_match3d_work_procedure_result(
result: Match3DWorkProcedureResult,
) -> Result<Match3DWorkProfileRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
let work = result.work.ok_or_else(|| {
SpacetimeClientError::Procedure(
"SpacetimeDB procedure 未返回 match3d work 快照".to_string(),
)
})?;
Ok(map_match3d_work_snapshot(work))
}
pub(crate) fn map_match3d_works_procedure_result(
result: Match3DWorksProcedureResult,
) -> Result<Vec<Match3DWorkProfileRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
Ok(result
.items
.into_iter()
.map(map_match3d_work_snapshot)
.collect())
}
pub(crate) fn map_match3d_run_procedure_result(
result: Match3DRunProcedureResult,
) -> Result<Match3DRunRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
let run = result.run.ok_or_else(|| {
SpacetimeClientError::Procedure("SpacetimeDB procedure 未返回 match3d run 快照".to_string())
})?;
Ok(map_match3d_run_snapshot(run))
}
pub(crate) fn map_match3d_click_item_procedure_result(
result: Match3DClickItemProcedureResult,
) -> Result<Match3DClickConfirmationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::Procedure(
result
.error_message
.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()),
));
}
let run = result.run.ok_or_else(|| {
SpacetimeClientError::Procedure(
"SpacetimeDB procedure 未返回 match3d click run 快照".to_string(),
)
})?;
let run = map_match3d_run_snapshot(run);
let accepted = result.status == "Accepted";
let accepted_item_instance_id = result.accepted_item_instance_id.clone();
let entered_slot_index = accepted_item_instance_id.as_deref().and_then(|item_id| {
run.items
.iter()
.find(|item| item.item_instance_id == item_id)
.and_then(|item| item.tray_slot_index)
});
Ok(Match3DClickConfirmationRecord {
status: result.status.clone(),
accepted,
reject_reason: if accepted { None } else { Some(result.status) },
accepted_item_instance_id,
entered_slot_index,
cleared_item_instance_ids: result.cleared_item_instance_ids,
failure_reason: result.failure_reason,
run,
})
}
fn map_match3d_agent_session_snapshot(
snapshot: Match3DAgentSessionSnapshot,
) -> Match3DAgentSessionRecord {
let config = map_match3d_creator_config(snapshot.config);
Match3DAgentSessionRecord {
session_id: snapshot.session_id,
current_turn: snapshot.current_turn,
progress_percent: snapshot.progress_percent,
stage: normalize_match3d_stage(&snapshot.stage).to_string(),
anchor_pack: build_match3d_anchor_pack(&config),
draft: snapshot
.draft
.map(|draft| map_match3d_result_draft(draft, config.reference_image_src.clone())),
config: Some(config),
messages: snapshot
.messages
.into_iter()
.map(map_match3d_agent_message_snapshot)
.collect(),
last_assistant_reply: empty_string_to_none(snapshot.last_assistant_reply),
published_profile_id: snapshot.published_profile_id,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_match3d_creator_config(
snapshot: Match3DCreatorConfigSnapshot,
) -> Match3DCreatorConfigRecord {
Match3DCreatorConfigRecord {
theme_text: snapshot.theme_text,
reference_image_src: snapshot.reference_image_src,
clear_count: snapshot.clear_count,
difficulty: snapshot.difficulty,
asset_style_id: snapshot.asset_style_id,
asset_style_label: snapshot.asset_style_label,
asset_style_prompt: snapshot.asset_style_prompt,
generate_click_sound: snapshot.generate_click_sound,
}
}
fn map_match3d_result_draft(
snapshot: Match3DDraftSnapshot,
reference_image_src: Option<String>,
) -> Match3DResultDraftRecord {
Match3DResultDraftRecord {
profile_id: snapshot.profile_id,
game_name: snapshot.game_name,
theme_text: snapshot.theme_text,
summary_text: snapshot.summary_text,
tags: snapshot.tags,
cover_image_src: None,
reference_image_src,
clear_count: snapshot.clear_count,
difficulty: snapshot.difficulty,
generated_item_assets_json: snapshot.generated_item_assets_json,
total_item_count: snapshot.clear_count.saturating_mul(3),
publish_ready: false,
blockers: Vec::new(),
}
}
fn map_match3d_agent_message_snapshot(
snapshot: Match3DAgentMessageSnapshot,
) -> Match3DAgentMessageRecord {
Match3DAgentMessageRecord {
message_id: snapshot.message_id,
role: snapshot.role,
kind: normalize_match3d_message_kind(&snapshot.kind).to_string(),
text: snapshot.text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
fn map_match3d_work_snapshot(snapshot: Match3DWorkSnapshot) -> Match3DWorkProfileRecord {
let config = map_match3d_creator_config(snapshot.config);
Match3DWorkProfileRecord {
work_id: snapshot.profile_id.clone(),
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
source_session_id: empty_string_to_none(snapshot.source_session_id),
author_display_name: snapshot.author_display_name,
game_name: snapshot.game_name,
theme_text: snapshot.theme_text,
summary: snapshot.summary_text,
tags: snapshot.tags,
cover_image_src: empty_string_to_none(snapshot.cover_image_src),
cover_asset_id: empty_string_to_none(snapshot.cover_asset_id),
reference_image_src: config.reference_image_src,
clear_count: snapshot.clear_count,
difficulty: snapshot.difficulty,
publication_status: normalize_match3d_publication_status(&snapshot.publication_status)
.to_string(),
play_count: snapshot.play_count,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
publish_ready: snapshot.publish_ready,
generated_item_assets_json: snapshot.generated_item_assets_json,
}
}
pub(crate) fn map_match3d_gallery_view_row(row: Match3DGalleryViewRow) -> Match3DWorkProfileRecord {
Match3DWorkProfileRecord {
work_id: row.profile_id.clone(),
profile_id: row.profile_id,
owner_user_id: row.owner_user_id,
source_session_id: empty_string_to_none(row.source_session_id),
author_display_name: row.author_display_name,
game_name: row.game_name,
theme_text: row.theme_text,
summary: row.summary_text,
tags: row.tags,
cover_image_src: empty_string_to_none(row.cover_image_src),
cover_asset_id: empty_string_to_none(row.cover_asset_id),
reference_image_src: row.reference_image_src,
clear_count: row.clear_count,
difficulty: row.difficulty,
publication_status: normalize_match3d_publication_status(&row.publication_status)
.to_string(),
play_count: row.play_count,
updated_at: format_timestamp_micros(row.updated_at_micros),
published_at: row.published_at_micros.map(format_timestamp_micros),
publish_ready: row.publish_ready,
generated_item_assets_json: row.generated_item_assets_json,
}
}
fn map_match3d_run_snapshot(snapshot: Match3DRunSnapshot) -> Match3DRunRecord {
let tray_slots = snapshot
.tray_slots
.into_iter()
.map(map_match3d_tray_slot_snapshot)
.collect::<Vec<_>>();
let items = snapshot
.items
.into_iter()
.map(|item| {
let tray_slot_index = tray_slots
.iter()
.find(|slot| {
slot.item_instance_id.as_deref() == Some(item.item_instance_id.as_str())
})
.map(|slot| slot.slot_index);
map_match3d_item_snapshot(item, tray_slot_index)
})
.collect();
Match3DRunRecord {
run_id: snapshot.run_id,
profile_id: snapshot.profile_id,
owner_user_id: String::new(),
status: snapshot.status,
snapshot_version: u64::from(snapshot.snapshot_version),
started_at_ms: i64_to_u64_ms(snapshot.started_at_ms),
duration_limit_ms: i64_to_u64_ms(snapshot.duration_limit_ms),
server_now_ms: Some(i64_to_u64_ms(snapshot.server_now_ms)),
remaining_ms: i64_to_u64_ms(snapshot.remaining_ms),
clear_count: snapshot.clear_count,
total_item_count: snapshot.total_item_count,
cleared_item_count: snapshot.cleared_item_count,
items,
tray_slots,
failure_reason: snapshot.failure_reason,
last_confirmed_action_id: None,
}
}
fn map_match3d_item_snapshot(
snapshot: Match3DItemSnapshot,
tray_slot_index: Option<u32>,
) -> Match3DItemSnapshotRecord {
Match3DItemSnapshotRecord {
item_instance_id: snapshot.item_instance_id,
item_type_id: snapshot.item_type_id,
visual_key: snapshot.visual_key,
x: snapshot.x,
y: snapshot.y,
radius: snapshot.radius,
layer: snapshot.layer,
state: snapshot.state,
clickable: snapshot.clickable,
tray_slot_index,
}
}
fn map_match3d_tray_slot_snapshot(snapshot: Match3DTraySlotSnapshot) -> Match3DTraySlotRecord {
Match3DTraySlotRecord {
slot_index: snapshot.slot_index,
item_instance_id: snapshot.item_instance_id,
item_type_id: snapshot.item_type_id,
visual_key: snapshot.visual_key,
}
}
fn build_match3d_anchor_pack(config: &Match3DCreatorConfigRecord) -> Match3DAnchorPackRecord {
let clear_count = config.clear_count.to_string();
let difficulty = config.difficulty.to_string();
Match3DAnchorPackRecord {
theme: build_match3d_anchor_item("theme", "题材主题", config.theme_text.as_str()),
clear_count: build_match3d_anchor_item("clearCount", "需要消除次数", clear_count.as_str()),
difficulty: build_match3d_anchor_item("difficulty", "难度", difficulty.as_str()),
}
}
fn build_match3d_anchor_item(key: &str, label: &str, value: &str) -> Match3DAnchorItemRecord {
Match3DAnchorItemRecord {
key: key.to_string(),
label: label.to_string(),
value: value.to_string(),
status: if value.trim().is_empty() {
"missing"
} else {
"confirmed"
}
.to_string(),
}
}
fn normalize_match3d_stage(value: &str) -> &str {
match value {
"Collecting" | "collecting" | "collecting_config" => "collecting_config",
"ReadyToCompile" | "ready_to_compile" => "ready_to_compile",
"DraftCompiled" | "draft_compiled" | "draft_ready" => "draft_ready",
"Published" | "published" => "published",
_ => value,
}
}
fn normalize_match3d_publication_status(value: &str) -> &str {
match value {
"Draft" | "draft" => "draft",
"Published" | "published" => "published",
_ => value,
}
}
fn normalize_match3d_message_kind(value: &str) -> &str {
match value {
"text" => "chat",
_ => value,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAgentSessionCreateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub welcome_message_id: String,
pub welcome_message_text: String,
pub config_json: Option<String>,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAgentMessageSubmitRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAgentMessageFinalizeRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub assistant_message_id: Option<String>,
pub assistant_reply_text: Option<String>,
pub config_json: Option<String>,
pub progress_percent: u32,
pub stage: String,
pub updated_at_micros: i64,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DCompileDraftRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub author_display_name: String,
pub game_name: Option<String>,
pub summary_text: Option<String>,
pub tags_json: Option<String>,
pub cover_image_src: Option<String>,
pub cover_asset_id: Option<String>,
pub compiled_at_micros: i64,
pub generated_item_assets_json: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DWorkUpdateRecordInput {
pub profile_id: String,
pub owner_user_id: String,
pub game_name: String,
pub theme_text: String,
pub summary_text: String,
pub tags_json: String,
pub cover_image_src: String,
pub cover_asset_id: String,
pub clear_count: u32,
pub difficulty: u32,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DRunStartRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub started_at_ms: i64,
pub item_type_count_override: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DRunClickRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub item_instance_id: String,
pub client_snapshot_version: u32,
pub client_event_id: String,
pub clicked_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DRunStopRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub stopped_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DRunRestartRecordInput {
pub source_run_id: String,
pub next_run_id: String,
pub owner_user_id: String,
pub restarted_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DRunTimeUpRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub finished_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAnchorItemRecord {
pub key: String,
pub label: String,
pub value: String,
pub status: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAnchorPackRecord {
pub theme: Match3DAnchorItemRecord,
pub clear_count: Match3DAnchorItemRecord,
pub difficulty: Match3DAnchorItemRecord,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DCreatorConfigRecord {
pub theme_text: String,
pub reference_image_src: Option<String>,
pub clear_count: u32,
pub difficulty: u32,
pub asset_style_id: Option<String>,
pub asset_style_label: Option<String>,
pub asset_style_prompt: Option<String>,
pub generate_click_sound: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DResultDraftRecord {
pub profile_id: String,
pub game_name: String,
pub theme_text: String,
pub summary_text: String,
pub tags: Vec<String>,
pub cover_image_src: Option<String>,
pub reference_image_src: Option<String>,
pub clear_count: u32,
pub difficulty: u32,
pub generated_item_assets_json: Option<String>,
pub total_item_count: u32,
pub publish_ready: bool,
pub blockers: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAgentMessageRecord {
pub message_id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DAgentSessionRecord {
pub session_id: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: Match3DAnchorPackRecord,
pub config: Option<Match3DCreatorConfigRecord>,
pub draft: Option<Match3DResultDraftRecord>,
pub messages: Vec<Match3DAgentMessageRecord>,
pub last_assistant_reply: Option<String>,
pub published_profile_id: Option<String>,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DWorkProfileRecord {
pub work_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub source_session_id: Option<String>,
pub author_display_name: String,
pub game_name: String,
pub theme_text: String,
pub summary: String,
pub tags: Vec<String>,
pub cover_image_src: Option<String>,
pub cover_asset_id: Option<String>,
pub reference_image_src: Option<String>,
pub clear_count: u32,
pub difficulty: u32,
pub publication_status: String,
pub play_count: u32,
pub updated_at: String,
pub published_at: Option<String>,
pub publish_ready: bool,
pub generated_item_assets_json: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Match3DItemSnapshotRecord {
pub item_instance_id: String,
pub item_type_id: String,
pub visual_key: String,
pub x: f32,
pub y: f32,
pub radius: f32,
pub layer: u32,
pub state: String,
pub clickable: bool,
pub tray_slot_index: Option<u32>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match3DTraySlotRecord {
pub slot_index: u32,
pub item_instance_id: Option<String>,
pub item_type_id: Option<String>,
pub visual_key: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Match3DRunRecord {
pub run_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub status: String,
pub snapshot_version: u64,
pub started_at_ms: u64,
pub duration_limit_ms: u64,
pub server_now_ms: Option<u64>,
pub remaining_ms: u64,
pub clear_count: u32,
pub total_item_count: u32,
pub cleared_item_count: u32,
pub items: Vec<Match3DItemSnapshotRecord>,
pub tray_slots: Vec<Match3DTraySlotRecord>,
pub failure_reason: Option<String>,
pub last_confirmed_action_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Match3DClickConfirmationRecord {
pub status: String,
pub accepted: bool,
pub reject_reason: Option<String>,
pub accepted_item_instance_id: Option<String>,
pub entered_slot_index: Option<u32>,
pub cleared_item_instance_ids: Vec<String>,
pub failure_reason: Option<String>,
pub run: Match3DRunRecord,
}

View File

@@ -0,0 +1,624 @@
use super::*;
impl From<DomainBattleStateInput> for BattleStateInput {
fn from(input: DomainBattleStateInput) -> Self {
Self {
battle_state_id: input.battle_state_id,
story_session_id: input.story_session_id,
runtime_session_id: input.runtime_session_id,
actor_user_id: input.actor_user_id,
chapter_id: input.chapter_id,
target_npc_id: input.target_npc_id,
target_name: input.target_name,
battle_mode: map_battle_mode(input.battle_mode),
player_hp: input.player_hp,
player_max_hp: input.player_max_hp,
player_mana: input.player_mana,
player_max_mana: input.player_max_mana,
target_hp: input.target_hp,
target_max_hp: input.target_max_hp,
experience_reward: input.experience_reward,
reward_items: input
.reward_items
.into_iter()
.map(map_runtime_item_reward_item_snapshot)
.collect(),
created_at_micros: input.created_at_micros,
}
}
}
pub(crate) fn map_npc_battle_interaction_procedure_result(
result: NpcBattleInteractionProcedureResult,
) -> Result<NpcBattleInteractionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let interaction_result = result
.result
.ok_or_else(|| SpacetimeClientError::missing_snapshot("NPC 开战结果"))?;
Ok(build_npc_battle_interaction_record(
map_npc_battle_interaction_result(interaction_result),
))
}
pub(crate) fn map_battle_state_snapshot(
snapshot: BattleStateSnapshot,
) -> DomainBattleStateSnapshot {
DomainBattleStateSnapshot {
battle_state_id: snapshot.battle_state_id,
story_session_id: snapshot.story_session_id,
runtime_session_id: snapshot.runtime_session_id,
actor_user_id: snapshot.actor_user_id,
chapter_id: snapshot.chapter_id,
target_npc_id: snapshot.target_npc_id,
target_name: snapshot.target_name,
battle_mode: map_battle_mode_back(snapshot.battle_mode),
status: map_battle_status(snapshot.status),
player_hp: snapshot.player_hp,
player_max_hp: snapshot.player_max_hp,
player_mana: snapshot.player_mana,
player_max_mana: snapshot.player_max_mana,
target_hp: snapshot.target_hp,
target_max_hp: snapshot.target_max_hp,
experience_reward: snapshot.experience_reward,
reward_items: snapshot
.reward_items
.into_iter()
.map(map_runtime_item_reward_item_snapshot_back)
.collect(),
turn_index: snapshot.turn_index,
last_action_function_id: snapshot.last_action_function_id,
last_action_text: snapshot.last_action_text,
last_result_text: snapshot.last_result_text,
last_damage_dealt: snapshot.last_damage_dealt,
last_damage_taken: snapshot.last_damage_taken,
last_outcome: map_combat_outcome(snapshot.last_outcome),
version: snapshot.version,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_npc_battle_interaction_result(
result: NpcBattleInteractionResult,
) -> NpcBattleInteractionSnapshot {
NpcBattleInteractionSnapshot {
interaction: map_npc_interaction_result(result.interaction),
battle_state: map_battle_state_snapshot(result.battle_state),
}
}
pub(crate) fn map_npc_interaction_result(
result: NpcInteractionResult,
) -> DomainNpcInteractionResult {
DomainNpcInteractionResult {
npc_state: map_npc_state_snapshot(result.npc_state),
interaction_status: map_npc_interaction_status(result.interaction_status),
action_text: result.action_text,
result_text: result.result_text,
story_text: result.story_text,
battle_mode: result.battle_mode.map(map_npc_interaction_battle_mode),
encounter_closed: result.encounter_closed,
affinity_changed: result.affinity_changed,
previous_affinity: result.previous_affinity,
next_affinity: result.next_affinity,
}
}
pub(crate) fn map_npc_state_snapshot(snapshot: NpcStateSnapshot) -> DomainNpcStateSnapshot {
DomainNpcStateSnapshot {
npc_state_id: snapshot.npc_state_id,
runtime_session_id: snapshot.runtime_session_id,
npc_id: snapshot.npc_id,
npc_name: snapshot.npc_name,
affinity: snapshot.affinity,
relation_state: map_npc_relation_state(snapshot.relation_state),
help_used: snapshot.help_used,
chatted_count: snapshot.chatted_count,
gifts_given: snapshot.gifts_given,
recruited: snapshot.recruited,
trade_stock_signature: snapshot.trade_stock_signature,
revealed_facts: snapshot.revealed_facts,
known_attribute_rumors: snapshot.known_attribute_rumors,
first_meaningful_contact_resolved: snapshot.first_meaningful_contact_resolved,
seen_backstory_chapter_ids: snapshot.seen_backstory_chapter_ids,
stance_profile: map_npc_stance_profile(snapshot.stance_profile),
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_npc_relation_state(value: NpcRelationState) -> DomainNpcRelationState {
DomainNpcRelationState {
affinity: value.affinity,
stance: map_npc_relation_stance(value.stance),
}
}
pub(crate) fn map_npc_stance_profile(value: NpcStanceProfile) -> DomainNpcStanceProfile {
DomainNpcStanceProfile {
trust: value.trust,
warmth: value.warmth,
ideological_fit: value.ideological_fit,
fear_or_guard: value.fear_or_guard,
loyalty: value.loyalty,
current_conflict_tag: value.current_conflict_tag,
recent_approvals: value.recent_approvals,
recent_disapprovals: value.recent_disapprovals,
}
}
pub(crate) fn map_npc_interaction_status(
value: NpcInteractionStatus,
) -> DomainNpcInteractionStatus {
match value {
NpcInteractionStatus::Previewed => DomainNpcInteractionStatus::Previewed,
NpcInteractionStatus::Dialogue => DomainNpcInteractionStatus::Dialogue,
NpcInteractionStatus::Resolved => DomainNpcInteractionStatus::Resolved,
NpcInteractionStatus::Recruited => DomainNpcInteractionStatus::Recruited,
NpcInteractionStatus::BattlePending => DomainNpcInteractionStatus::BattlePending,
NpcInteractionStatus::Left => DomainNpcInteractionStatus::Left,
}
}
pub(crate) fn map_npc_interaction_battle_mode(
value: NpcInteractionBattleMode,
) -> DomainNpcInteractionBattleMode {
match value {
NpcInteractionBattleMode::Fight => DomainNpcInteractionBattleMode::Fight,
NpcInteractionBattleMode::Spar => DomainNpcInteractionBattleMode::Spar,
}
}
pub(crate) fn map_npc_relation_stance(value: NpcRelationStance) -> DomainNpcRelationStance {
match value {
NpcRelationStance::Hostile => DomainNpcRelationStance::Hostile,
NpcRelationStance::Guarded => DomainNpcRelationStance::Guarded,
NpcRelationStance::Neutral => DomainNpcRelationStance::Neutral,
NpcRelationStance::Cooperative => DomainNpcRelationStance::Cooperative,
NpcRelationStance::Bonded => DomainNpcRelationStance::Bonded,
}
}
pub(crate) fn map_ai_task_kind(value: DomainAiTaskKind) -> AiTaskKind {
match value {
DomainAiTaskKind::StoryGeneration => AiTaskKind::StoryGeneration,
DomainAiTaskKind::CharacterChat => AiTaskKind::CharacterChat,
DomainAiTaskKind::NpcChat => AiTaskKind::NpcChat,
DomainAiTaskKind::CustomWorldGeneration => AiTaskKind::CustomWorldGeneration,
DomainAiTaskKind::QuestIntent => AiTaskKind::QuestIntent,
DomainAiTaskKind::RuntimeItemIntent => AiTaskKind::RuntimeItemIntent,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BattleStateRecord {
pub battle_state_id: String,
pub story_session_id: String,
pub runtime_session_id: String,
pub actor_user_id: String,
pub chapter_id: Option<String>,
pub target_npc_id: String,
pub target_name: String,
pub battle_mode: String,
pub status: String,
pub player_hp: i32,
pub player_max_hp: i32,
pub player_mana: i32,
pub player_max_mana: i32,
pub target_hp: i32,
pub target_max_hp: i32,
pub experience_reward: u32,
pub reward_items: Vec<DomainRuntimeItemRewardItemSnapshot>,
pub turn_index: u32,
pub last_action_function_id: Option<String>,
pub last_action_text: Option<String>,
pub last_result_text: Option<String>,
pub last_damage_dealt: i32,
pub last_damage_taken: i32,
pub last_outcome: String,
pub version: u32,
pub created_at: String,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldLibraryEntryRecord {
pub owner_user_id: String,
pub profile_id: String,
pub public_work_code: Option<String>,
pub author_public_user_code: Option<String>,
pub profile: serde_json::Value,
pub visibility: String,
pub published_at: Option<String>,
pub updated_at: String,
pub author_display_name: String,
pub world_name: String,
pub subtitle: String,
pub summary_text: String,
pub cover_image_src: Option<String>,
pub theme_mode: String,
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,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldGalleryEntryRecord {
pub owner_user_id: String,
pub profile_id: String,
pub public_work_code: String,
pub author_public_user_code: String,
pub visibility: String,
pub published_at: Option<String>,
pub updated_at: String,
pub author_display_name: String,
pub world_name: String,
pub subtitle: String,
pub summary_text: String,
pub cover_image_src: Option<String>,
pub theme_mode: String,
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,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CustomWorldPublishedProfileCompileRecord {
pub profile_id: String,
pub owner_user_id: String,
pub world_name: String,
pub subtitle: String,
pub summary_text: String,
pub theme_mode: String,
pub cover_image_src: Option<String>,
pub playable_npc_count: u32,
pub landmark_count: u32,
pub author_display_name: String,
pub compiled_profile: serde_json::Value,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldWorkSummaryRecord {
pub work_id: String,
pub source_type: String,
pub status: String,
pub title: String,
pub subtitle: String,
pub summary: String,
pub cover_image_src: Option<String>,
pub cover_render_mode: Option<String>,
pub cover_character_image_srcs: Vec<String>,
pub updated_at: String,
pub published_at: Option<String>,
pub stage: Option<String>,
pub stage_label: Option<String>,
pub playable_npc_count: u32,
pub landmark_count: u32,
pub role_visual_ready_count: Option<u32>,
pub role_animation_ready_count: Option<u32>,
pub role_asset_summary_label: Option<String>,
pub session_id: Option<String>,
pub profile_id: Option<String>,
pub can_resume: bool,
pub can_enter_world: bool,
pub blocker_count: u32,
pub publish_ready: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldProfileUpsertRecordInput {
pub profile_id: String,
pub owner_user_id: String,
pub public_work_code: Option<String>,
pub author_public_user_code: Option<String>,
pub source_agent_session_id: Option<String>,
pub world_name: String,
pub subtitle: String,
pub summary_text: String,
pub theme_mode: DomainCustomWorldThemeMode,
pub cover_image_src: Option<String>,
pub profile_payload_json: String,
pub playable_npc_count: u32,
pub landmark_count: u32,
pub author_display_name: String,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolveNpcBattleInteractionInput {
pub npc_interaction: DomainResolveNpcInteractionInput,
pub story_session_id: String,
pub actor_user_id: String,
pub battle_state_id: Option<String>,
pub player_hp: i32,
pub player_max_hp: i32,
pub player_mana: i32,
pub player_max_mana: i32,
pub target_hp: i32,
pub target_max_hp: i32,
pub experience_reward: u32,
pub reward_items: Vec<DomainRuntimeItemRewardItemSnapshot>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NpcStateRecord {
pub npc_state_id: String,
pub runtime_session_id: String,
pub npc_id: String,
pub npc_name: String,
pub affinity: i32,
pub relation_stance: String,
pub help_used: bool,
pub chatted_count: u32,
pub gifts_given: u32,
pub recruited: bool,
pub trade_stock_signature: Option<String>,
pub revealed_facts: Vec<String>,
pub known_attribute_rumors: Vec<String>,
pub first_meaningful_contact_resolved: bool,
pub seen_backstory_chapter_ids: Vec<String>,
pub trust: u8,
pub warmth: u8,
pub ideological_fit: u8,
pub fear_or_guard: u8,
pub loyalty: u8,
pub current_conflict_tag: Option<String>,
pub recent_approvals: Vec<String>,
pub recent_disapprovals: Vec<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NpcInteractionRecord {
pub npc_state: NpcStateRecord,
pub interaction_status: String,
pub action_text: String,
pub result_text: String,
pub story_text: Option<String>,
pub battle_mode: Option<String>,
pub encounter_closed: bool,
pub affinity_changed: bool,
pub previous_affinity: i32,
pub next_affinity: i32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NpcBattleInteractionRecord {
pub npc_interaction: NpcInteractionRecord,
pub battle_state: BattleStateRecord,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct NpcBattleInteractionSnapshot {
interaction: DomainNpcInteractionResult,
battle_state: DomainBattleStateSnapshot,
}
pub(crate) fn build_battle_state_record(snapshot: DomainBattleStateSnapshot) -> BattleStateRecord {
BattleStateRecord {
battle_state_id: snapshot.battle_state_id,
story_session_id: snapshot.story_session_id,
runtime_session_id: snapshot.runtime_session_id,
actor_user_id: snapshot.actor_user_id,
chapter_id: snapshot.chapter_id,
target_npc_id: snapshot.target_npc_id,
target_name: snapshot.target_name,
battle_mode: snapshot.battle_mode.as_str().to_string(),
status: snapshot.status.as_str().to_string(),
player_hp: snapshot.player_hp,
player_max_hp: snapshot.player_max_hp,
player_mana: snapshot.player_mana,
player_max_mana: snapshot.player_max_mana,
target_hp: snapshot.target_hp,
target_max_hp: snapshot.target_max_hp,
experience_reward: snapshot.experience_reward,
reward_items: snapshot.reward_items,
turn_index: snapshot.turn_index,
last_action_function_id: snapshot.last_action_function_id,
last_action_text: snapshot.last_action_text,
last_result_text: snapshot.last_result_text,
last_damage_dealt: snapshot.last_damage_dealt,
last_damage_taken: snapshot.last_damage_taken,
last_outcome: snapshot.last_outcome.as_str().to_string(),
version: snapshot.version,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
impl From<ResolveNpcBattleInteractionInput>
for crate::module_bindings::ResolveNpcBattleInteractionInput
{
fn from(input: ResolveNpcBattleInteractionInput) -> Self {
Self {
npc_interaction: crate::module_bindings::ResolveNpcInteractionInput {
runtime_session_id: input.npc_interaction.runtime_session_id,
npc_id: input.npc_interaction.npc_id,
npc_name: input.npc_interaction.npc_name,
interaction_function_id: input.npc_interaction.interaction_function_id,
release_npc_id: input.npc_interaction.release_npc_id,
updated_at_micros: input.npc_interaction.updated_at_micros,
},
story_session_id: input.story_session_id,
actor_user_id: input.actor_user_id,
battle_state_id: input.battle_state_id,
player_hp: input.player_hp,
player_max_hp: input.player_max_hp,
player_mana: input.player_mana,
player_max_mana: input.player_max_mana,
target_hp: input.target_hp,
target_max_hp: input.target_max_hp,
experience_reward: input.experience_reward,
reward_items: input
.reward_items
.into_iter()
.map(map_runtime_item_reward_item_snapshot)
.collect(),
}
}
}
pub(crate) fn validate_npc_battle_interaction_input(
input: &ResolveNpcBattleInteractionInput,
) -> Result<(), SpacetimeClientError> {
let battle_state_input = DomainBattleStateInput {
battle_state_id: input
.battle_state_id
.clone()
.unwrap_or_else(|| "battle_preview".to_string()),
story_session_id: input.story_session_id.clone(),
runtime_session_id: input.npc_interaction.runtime_session_id.clone(),
actor_user_id: input.actor_user_id.clone(),
chapter_id: None,
target_npc_id: input.npc_interaction.npc_id.clone(),
target_name: input.npc_interaction.npc_name.clone(),
battle_mode: DomainBattleMode::Fight,
player_hp: input.player_hp,
player_max_hp: input.player_max_hp,
player_mana: input.player_mana,
player_max_mana: input.player_max_mana,
target_hp: input.target_hp,
target_max_hp: input.target_max_hp,
experience_reward: input.experience_reward,
reward_items: input.reward_items.clone(),
created_at_micros: input.npc_interaction.updated_at_micros,
};
validate_battle_state_input(&battle_state_input)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?;
for reward_item in input.reward_items.iter().cloned() {
normalize_reward_item_snapshot(reward_item)
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?;
}
Ok(())
}
pub(crate) fn build_npc_state_record(snapshot: DomainNpcStateSnapshot) -> NpcStateRecord {
NpcStateRecord {
npc_state_id: snapshot.npc_state_id,
runtime_session_id: snapshot.runtime_session_id,
npc_id: snapshot.npc_id,
npc_name: snapshot.npc_name,
affinity: snapshot.affinity,
relation_stance: format_npc_relation_stance(snapshot.relation_state.stance).to_string(),
help_used: snapshot.help_used,
chatted_count: snapshot.chatted_count,
gifts_given: snapshot.gifts_given,
recruited: snapshot.recruited,
trade_stock_signature: snapshot.trade_stock_signature,
revealed_facts: snapshot.revealed_facts,
known_attribute_rumors: snapshot.known_attribute_rumors,
first_meaningful_contact_resolved: snapshot.first_meaningful_contact_resolved,
seen_backstory_chapter_ids: snapshot.seen_backstory_chapter_ids,
trust: snapshot.stance_profile.trust,
warmth: snapshot.stance_profile.warmth,
ideological_fit: snapshot.stance_profile.ideological_fit,
fear_or_guard: snapshot.stance_profile.fear_or_guard,
loyalty: snapshot.stance_profile.loyalty,
current_conflict_tag: snapshot.stance_profile.current_conflict_tag,
recent_approvals: snapshot.stance_profile.recent_approvals,
recent_disapprovals: snapshot.stance_profile.recent_disapprovals,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
pub(crate) fn build_npc_interaction_record(
result: DomainNpcInteractionResult,
) -> NpcInteractionRecord {
NpcInteractionRecord {
npc_state: build_npc_state_record(result.npc_state),
interaction_status: format_npc_interaction_status(result.interaction_status).to_string(),
action_text: result.action_text,
result_text: result.result_text,
story_text: result.story_text,
battle_mode: result
.battle_mode
.map(|mode| format_npc_interaction_battle_mode(mode).to_string()),
encounter_closed: result.encounter_closed,
affinity_changed: result.affinity_changed,
previous_affinity: result.previous_affinity,
next_affinity: result.next_affinity,
}
}
pub(crate) fn build_npc_battle_interaction_record(
result: NpcBattleInteractionSnapshot,
) -> NpcBattleInteractionRecord {
NpcBattleInteractionRecord {
npc_interaction: build_npc_interaction_record(result.interaction),
battle_state: build_battle_state_record(result.battle_state),
}
}
pub(crate) fn format_npc_relation_stance(value: DomainNpcRelationStance) -> &'static str {
match value {
DomainNpcRelationStance::Hostile => "hostile",
DomainNpcRelationStance::Guarded => "guarded",
DomainNpcRelationStance::Neutral => "neutral",
DomainNpcRelationStance::Cooperative => "cooperative",
DomainNpcRelationStance::Bonded => "bonded",
}
}
pub(crate) fn format_npc_interaction_status(value: DomainNpcInteractionStatus) -> &'static str {
match value {
DomainNpcInteractionStatus::Previewed => "previewed",
DomainNpcInteractionStatus::Dialogue => "dialogue",
DomainNpcInteractionStatus::Resolved => "resolved",
DomainNpcInteractionStatus::Recruited => "recruited",
DomainNpcInteractionStatus::BattlePending => "battle_pending",
DomainNpcInteractionStatus::Left => "left",
}
}
pub(crate) fn format_npc_interaction_battle_mode(
value: DomainNpcInteractionBattleMode,
) -> &'static str {
match value {
DomainNpcInteractionBattleMode::Fight => "fight",
DomainNpcInteractionBattleMode::Spar => "spar",
}
}
pub(crate) fn map_inventory_item_source_kind(
value: InventoryItemSourceKind,
) -> module_inventory::InventoryItemSourceKind {
match value {
InventoryItemSourceKind::StoryReward => {
module_inventory::InventoryItemSourceKind::StoryReward
}
InventoryItemSourceKind::QuestReward => {
module_inventory::InventoryItemSourceKind::QuestReward
}
InventoryItemSourceKind::TreasureReward => {
module_inventory::InventoryItemSourceKind::TreasureReward
}
InventoryItemSourceKind::NpcGift => module_inventory::InventoryItemSourceKind::NpcGift,
InventoryItemSourceKind::NpcTrade => module_inventory::InventoryItemSourceKind::NpcTrade,
InventoryItemSourceKind::CombatDrop => {
module_inventory::InventoryItemSourceKind::CombatDrop
}
InventoryItemSourceKind::ForgeCraft => {
module_inventory::InventoryItemSourceKind::ForgeCraft
}
InventoryItemSourceKind::ForgeReforge => {
module_inventory::InventoryItemSourceKind::ForgeReforge
}
InventoryItemSourceKind::ManualPatch => {
module_inventory::InventoryItemSourceKind::ManualPatch
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,440 @@
use super::*;
impl From<module_runtime::CreationEntryTypeAdminUpsertInput> for CreationEntryTypeAdminUpsertInput {
fn from(input: module_runtime::CreationEntryTypeAdminUpsertInput) -> Self {
Self {
id: input.id,
title: input.title,
subtitle: input.subtitle,
badge: input.badge,
image_src: input.image_src,
visible: input.visible,
open: input.open,
sort_order: input.sort_order,
}
}
}
impl From<module_runtime::RuntimeSettingGetInput> for RuntimeSettingGetInput {
fn from(input: module_runtime::RuntimeSettingGetInput) -> Self {
Self {
user_id: input.user_id,
}
}
}
impl From<module_runtime::RuntimeSettingUpsertInput> for RuntimeSettingUpsertInput {
fn from(input: module_runtime::RuntimeSettingUpsertInput) -> Self {
Self {
user_id: input.user_id,
music_volume: input.music_volume,
platform_theme: map_runtime_platform_theme(input.platform_theme),
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<module_runtime::RuntimeBrowseHistoryListInput> for RuntimeBrowseHistoryListInput {
fn from(input: module_runtime::RuntimeBrowseHistoryListInput) -> Self {
Self {
user_id: input.user_id,
}
}
}
impl From<module_runtime::RuntimeBrowseHistoryClearInput> for RuntimeBrowseHistoryClearInput {
fn from(input: module_runtime::RuntimeBrowseHistoryClearInput) -> Self {
Self {
user_id: input.user_id,
}
}
}
impl From<module_runtime::RuntimeBrowseHistorySyncInput> for RuntimeBrowseHistorySyncInput {
fn from(input: module_runtime::RuntimeBrowseHistorySyncInput) -> Self {
Self {
user_id: input.user_id,
entries: input.entries.into_iter().map(Into::into).collect(),
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<module_runtime::RuntimeBrowseHistoryWriteInput> for RuntimeBrowseHistoryWriteInput {
fn from(input: module_runtime::RuntimeBrowseHistoryWriteInput) -> Self {
Self {
owner_user_id: input.owner_user_id,
profile_id: input.profile_id,
world_name: input.world_name,
subtitle: input.subtitle,
summary_text: input.summary_text,
cover_image_src: input.cover_image_src,
theme_mode: input.theme_mode,
author_display_name: input.author_display_name,
visited_at: input.visited_at,
}
}
}
impl From<module_runtime::RuntimeSnapshotGetInput> for RuntimeSnapshotGetInput {
fn from(input: module_runtime::RuntimeSnapshotGetInput) -> Self {
Self {
user_id: input.user_id,
}
}
}
impl From<module_runtime::RuntimeSnapshotDeleteInput> for RuntimeSnapshotDeleteInput {
fn from(input: module_runtime::RuntimeSnapshotDeleteInput) -> Self {
Self {
user_id: input.user_id,
}
}
}
pub type CreationEntryConfigRecord =
shared_contracts::creation_entry_config::CreationEntryConfigResponse;
pub(crate) fn map_creation_entry_config_procedure_result(
result: CreationEntryConfigProcedureResult,
) -> Result<CreationEntryConfigRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let snapshot = result
.record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("创作入口配置快照"))?;
Ok(module_runtime::build_creation_entry_config_response(
map_creation_entry_config_snapshot(snapshot),
))
}
pub(crate) fn build_creation_entry_config_record_from_rows(
header: CreationEntryConfig,
mut creation_types: Vec<CreationEntryTypeConfig>,
) -> CreationEntryConfigRecord {
creation_types.sort_by(|left, right| {
left.sort_order
.cmp(&right.sort_order)
.then_with(|| left.id.cmp(&right.id))
});
module_runtime::build_creation_entry_config_response(
module_runtime::CreationEntryConfigSnapshot {
config_id: header.config_id,
start_card: module_runtime::CreationEntryStartCardSnapshot {
title: header.start_title,
description: header.start_description,
idle_badge: header.start_idle_badge,
busy_badge: header.start_busy_badge,
},
type_modal: module_runtime::CreationEntryTypeModalSnapshot {
title: header.modal_title,
description: header.modal_description,
},
creation_types: creation_types
.into_iter()
.map(|item| module_runtime::CreationEntryTypeSnapshot {
id: item.id,
title: item.title,
subtitle: item.subtitle,
badge: item.badge,
image_src: item.image_src,
visible: item.visible,
open: item.open,
sort_order: item.sort_order,
updated_at_micros: item.updated_at.to_micros_since_unix_epoch(),
})
.collect(),
updated_at_micros: header.updated_at.to_micros_since_unix_epoch(),
},
)
}
fn map_creation_entry_config_snapshot(
snapshot: CreationEntryConfigSnapshot,
) -> module_runtime::CreationEntryConfigSnapshot {
module_runtime::CreationEntryConfigSnapshot {
config_id: snapshot.config_id,
start_card: module_runtime::CreationEntryStartCardSnapshot {
title: snapshot.start_card.title,
description: snapshot.start_card.description,
idle_badge: snapshot.start_card.idle_badge,
busy_badge: snapshot.start_card.busy_badge,
},
type_modal: module_runtime::CreationEntryTypeModalSnapshot {
title: snapshot.type_modal.title,
description: snapshot.type_modal.description,
},
creation_types: snapshot
.creation_types
.into_iter()
.map(|item| module_runtime::CreationEntryTypeSnapshot {
id: item.id,
title: item.title,
subtitle: item.subtitle,
badge: item.badge,
image_src: item.image_src,
visible: item.visible,
open: item.open,
sort_order: item.sort_order,
updated_at_micros: item.updated_at_micros,
})
.collect(),
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_runtime_setting_procedure_result(
result: RuntimeSettingProcedureResult,
) -> Result<RuntimeSettingsRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let snapshot = result
.record
.ok_or_else(|| SpacetimeClientError::missing_snapshot("runtime settings 快照"))?;
Ok(build_runtime_setting_record(map_runtime_setting_snapshot(
snapshot,
)))
}
pub(crate) fn map_runtime_tracking_event_procedure_result(
result: RuntimeTrackingEventProcedureResult,
) -> Result<(), SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(())
}
pub(crate) fn map_runtime_snapshot_procedure_result(
result: RuntimeSnapshotProcedureResult,
) -> Result<Option<RuntimeSnapshotRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.record
.map(|snapshot| {
build_runtime_snapshot_record(map_runtime_snapshot_snapshot(snapshot))
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))
})
.transpose()
}
pub(crate) fn map_runtime_snapshot_required_procedure_result(
result: RuntimeSnapshotProcedureResult,
) -> Result<RuntimeSnapshotRecord, SpacetimeClientError> {
map_runtime_snapshot_procedure_result(result)?
.ok_or_else(|| SpacetimeClientError::missing_snapshot("runtime snapshot 快照"))
}
pub(crate) fn map_runtime_snapshot_delete_procedure_result(
result: RuntimeSnapshotProcedureResult,
) -> Result<bool, SpacetimeClientError> {
map_runtime_snapshot_procedure_result(result).map(|record| record.is_some())
}
pub(crate) fn map_runtime_setting_snapshot(
snapshot: RuntimeSettingSnapshot,
) -> module_runtime::RuntimeSettingSnapshot {
module_runtime::RuntimeSettingSnapshot {
user_id: snapshot.user_id,
music_volume: snapshot.music_volume,
platform_theme: map_runtime_platform_theme_back(snapshot.platform_theme),
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_runtime_platform_theme(
value: DomainRuntimePlatformTheme,
) -> crate::module_bindings::RuntimePlatformTheme {
match value {
DomainRuntimePlatformTheme::Light => crate::module_bindings::RuntimePlatformTheme::Light,
DomainRuntimePlatformTheme::Dark => crate::module_bindings::RuntimePlatformTheme::Dark,
}
}
pub(crate) fn map_runtime_platform_theme_back(
value: crate::module_bindings::RuntimePlatformTheme,
) -> DomainRuntimePlatformTheme {
match value {
crate::module_bindings::RuntimePlatformTheme::Light => DomainRuntimePlatformTheme::Light,
crate::module_bindings::RuntimePlatformTheme::Dark => DomainRuntimePlatformTheme::Dark,
}
}
pub(crate) fn map_runtime_tracking_scope_kind(
value: DomainRuntimeTrackingScopeKind,
) -> crate::module_bindings::RuntimeTrackingScopeKind {
match value {
DomainRuntimeTrackingScopeKind::Site => {
crate::module_bindings::RuntimeTrackingScopeKind::Site
}
DomainRuntimeTrackingScopeKind::Work => {
crate::module_bindings::RuntimeTrackingScopeKind::Work
}
DomainRuntimeTrackingScopeKind::Module => {
crate::module_bindings::RuntimeTrackingScopeKind::Module
}
DomainRuntimeTrackingScopeKind::User => {
crate::module_bindings::RuntimeTrackingScopeKind::User
}
}
}
pub(crate) fn map_runtime_tracking_scope_kind_back(
value: crate::module_bindings::RuntimeTrackingScopeKind,
) -> DomainRuntimeTrackingScopeKind {
match value {
crate::module_bindings::RuntimeTrackingScopeKind::Site => {
DomainRuntimeTrackingScopeKind::Site
}
crate::module_bindings::RuntimeTrackingScopeKind::Work => {
DomainRuntimeTrackingScopeKind::Work
}
crate::module_bindings::RuntimeTrackingScopeKind::Module => {
DomainRuntimeTrackingScopeKind::Module
}
crate::module_bindings::RuntimeTrackingScopeKind::User => {
DomainRuntimeTrackingScopeKind::User
}
}
}
pub(crate) fn parse_json_value(
value: &str,
label: &str,
) -> Result<serde_json::Value, SpacetimeClientError> {
serde_json::from_str::<serde_json::Value>(value)
.map_err(|error| SpacetimeClientError::Runtime(format!("{label} 非法: {error}")))
}
pub(crate) fn parse_json_array(
value: &str,
label: &str,
) -> Result<Vec<serde_json::Value>, SpacetimeClientError> {
match parse_json_value(value, label)? {
serde_json::Value::Array(entries) => Ok(entries),
_ => Err(SpacetimeClientError::Runtime(format!(
"{label} 必须是 JSON array"
))),
}
}
pub(crate) fn parse_json_string_array(
value: &str,
label: &str,
) -> Result<Vec<String>, SpacetimeClientError> {
parse_json_array(value, label)?
.into_iter()
.map(|entry| match entry {
serde_json::Value::String(value) => Ok(value),
_ => Err(SpacetimeClientError::Runtime(format!(
"{label} 必须是 string array"
))),
})
.collect()
}
pub(crate) fn parse_supported_actions_json(
value: &str,
) -> Result<Vec<CustomWorldSupportedActionRecord>, SpacetimeClientError> {
parse_json_array(value, "custom world agent supported_actions_json")?
.into_iter()
.map(|entry| {
let object = entry.as_object().ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world supported action 必须是 JSON object".to_string(),
)
})?;
let action = object
.get("action")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world supported action.action 缺失".to_string(),
)
})?;
let enabled = object
.get("enabled")
.and_then(serde_json::Value::as_bool)
.ok_or_else(|| {
SpacetimeClientError::Runtime(
"custom world supported action.enabled 缺失".to_string(),
)
})?;
Ok(CustomWorldSupportedActionRecord {
action: action.to_string(),
enabled,
reason: object
.get("reason")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned),
})
})
.collect()
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishRuntimeParamsRecord {
pub level_count: u32,
pub merge_count_per_upgrade: u32,
pub spawn_target_count: u32,
pub leader_move_speed: f32,
pub follower_catch_up_speed: f32,
pub offscreen_cull_seconds: f32,
pub prey_spawn_delta_levels: Vec<u32>,
pub threat_spawn_delta_levels: Vec<u32>,
pub win_level: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishGameDraftRecord {
pub title: String,
pub subtitle: String,
pub core_fun: String,
pub ecology_theme: String,
pub levels: Vec<BigFishLevelBlueprintRecord>,
pub background: BigFishBackgroundBlueprintRecord,
pub runtime_params: BigFishRuntimeParamsRecord,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishRuntimeEntityRecord {
pub entity_id: String,
pub level: u32,
pub position: BigFishVector2Record,
pub radius: f32,
pub offscreen_seconds: f32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BigFishRuntimeRunRecord {
pub run_id: String,
pub session_id: String,
pub status: String,
pub tick: u64,
pub player_level: u32,
pub win_level: u32,
pub leader_entity_id: Option<String>,
pub owned_entities: Vec<BigFishRuntimeEntityRecord>,
pub wild_entities: Vec<BigFishRuntimeEntityRecord>,
pub camera_center: BigFishVector2Record,
pub last_input: BigFishVector2Record,
pub event_log: Vec<String>,
pub updated_at: String,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,417 @@
use super::*;
pub(crate) fn map_square_hole_agent_session_procedure_result(
result: SquareHoleAgentSessionProcedureResult,
) -> Result<SquareHoleAgentSessionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole agent session 快照"))?;
Ok(map_square_hole_agent_session_snapshot(session))
}
pub(crate) fn map_square_hole_work_procedure_result(
result: SquareHoleWorkProcedureResult,
) -> Result<SquareHoleWorkProfileRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let work = result
.work
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole work 快照"))?;
Ok(map_square_hole_work_snapshot(work))
}
pub(crate) fn map_square_hole_works_procedure_result(
result: SquareHoleWorksProcedureResult,
) -> Result<Vec<SquareHoleWorkProfileRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.items
.into_iter()
.map(map_square_hole_work_snapshot)
.collect())
}
pub(crate) fn map_square_hole_run_procedure_result(
result: SquareHoleRunProcedureResult,
) -> Result<SquareHoleRunRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run = result
.run
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole run 快照"))?;
Ok(map_square_hole_run_snapshot(run))
}
pub(crate) fn map_square_hole_drop_shape_procedure_result(
result: SquareHoleDropShapeProcedureResult,
) -> Result<SquareHoleDropConfirmationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run = result
.run
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole drop run 快照"))?;
let feedback = result
.feedback
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole drop feedback 快照"))?;
let run = map_square_hole_run_snapshot(run);
Ok(SquareHoleDropConfirmationRecord {
status: result.status,
accepted: feedback.accepted,
reject_reason: feedback.reject_reason.clone(),
failure_reason: result.failure_reason,
feedback: map_square_hole_feedback_snapshot(feedback),
run,
})
}
fn map_square_hole_agent_session_snapshot(
snapshot: SquareHoleAgentSessionSnapshot,
) -> SquareHoleAgentSessionRecord {
let config = map_square_hole_creator_config(snapshot.config);
SquareHoleAgentSessionRecord {
session_id: snapshot.session_id,
current_turn: snapshot.current_turn,
progress_percent: snapshot.progress_percent,
stage: normalize_square_hole_stage(&snapshot.stage).to_string(),
anchor_pack: build_square_hole_anchor_pack(&config),
config,
draft: snapshot.draft.map(map_square_hole_result_draft),
messages: snapshot
.messages
.into_iter()
.map(map_square_hole_agent_message_snapshot)
.collect(),
last_assistant_reply: empty_string_to_none(snapshot.last_assistant_reply),
published_profile_id: snapshot.published_profile_id,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_square_hole_creator_config(
snapshot: SquareHoleCreatorConfigSnapshot,
) -> SquareHoleCreatorConfigRecord {
SquareHoleCreatorConfigRecord {
theme_text: snapshot.theme_text,
twist_rule: snapshot.twist_rule,
shape_count: snapshot.shape_count,
difficulty: snapshot.difficulty,
shape_options: snapshot
.shape_options
.into_iter()
.map(map_square_hole_shape_option)
.collect(),
hole_options: snapshot
.hole_options
.into_iter()
.map(map_square_hole_hole_option)
.collect(),
background_prompt: snapshot.background_prompt,
cover_image_src: empty_string_to_none(snapshot.cover_image_src),
background_image_src: empty_string_to_none(snapshot.background_image_src),
}
}
fn map_square_hole_result_draft(snapshot: SquareHoleDraftSnapshot) -> SquareHoleResultDraftRecord {
SquareHoleResultDraftRecord {
profile_id: snapshot.profile_id,
game_name: snapshot.game_name,
theme_text: snapshot.theme_text,
twist_rule: snapshot.twist_rule,
summary: snapshot.summary_text,
tags: snapshot.tags,
cover_image_src: empty_string_to_none(snapshot.cover_image_src),
background_prompt: snapshot.background_prompt,
background_image_src: empty_string_to_none(snapshot.background_image_src),
shape_options: snapshot
.shape_options
.into_iter()
.map(map_square_hole_shape_option)
.collect(),
hole_options: snapshot
.hole_options
.into_iter()
.map(map_square_hole_hole_option)
.collect(),
shape_count: snapshot.shape_count,
difficulty: snapshot.difficulty,
publish_ready: false,
blockers: Vec::new(),
}
}
fn map_square_hole_agent_message_snapshot(
snapshot: SquareHoleAgentMessageSnapshot,
) -> SquareHoleAgentMessageRecord {
SquareHoleAgentMessageRecord {
id: snapshot.message_id,
role: snapshot.role,
kind: normalize_square_hole_message_kind(&snapshot.kind).to_string(),
text: snapshot.text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
fn map_square_hole_work_snapshot(snapshot: SquareHoleWorkSnapshot) -> SquareHoleWorkProfileRecord {
SquareHoleWorkProfileRecord {
work_id: snapshot.work_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
source_session_id: empty_string_to_none(snapshot.source_session_id),
author_display_name: snapshot.author_display_name,
game_name: snapshot.game_name,
theme_text: snapshot.theme_text,
twist_rule: snapshot.twist_rule,
summary: snapshot.summary_text,
tags: snapshot.tags,
cover_image_src: empty_string_to_none(snapshot.cover_image_src),
background_prompt: snapshot.background_prompt,
background_image_src: empty_string_to_none(snapshot.background_image_src),
shape_options: snapshot
.shape_options
.into_iter()
.map(map_square_hole_shape_option)
.collect(),
hole_options: snapshot
.hole_options
.into_iter()
.map(map_square_hole_hole_option)
.collect(),
shape_count: snapshot.shape_count,
difficulty: snapshot.difficulty,
publication_status: normalize_square_hole_publication_status(&snapshot.publication_status)
.to_string(),
play_count: snapshot.play_count,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
publish_ready: snapshot.publish_ready,
}
}
pub(crate) fn map_square_hole_gallery_view_row(
row: SquareHoleGalleryViewRow,
) -> SquareHoleWorkProfileRecord {
SquareHoleWorkProfileRecord {
work_id: row.work_id,
profile_id: row.profile_id,
owner_user_id: row.owner_user_id,
source_session_id: empty_string_to_none(row.source_session_id),
author_display_name: row.author_display_name,
game_name: row.game_name,
theme_text: row.theme_text,
twist_rule: row.twist_rule,
summary: row.summary_text,
tags: row.tags,
cover_image_src: empty_string_to_none(row.cover_image_src),
background_prompt: row.background_prompt,
background_image_src: empty_string_to_none(row.background_image_src),
shape_options: row
.shape_options
.into_iter()
.map(map_square_hole_shape_option)
.collect(),
hole_options: row
.hole_options
.into_iter()
.map(map_square_hole_hole_option)
.collect(),
shape_count: row.shape_count,
difficulty: row.difficulty,
publication_status: normalize_square_hole_publication_status(&row.publication_status)
.to_string(),
play_count: row.play_count,
updated_at: format_timestamp_micros(row.updated_at_micros),
published_at: row.published_at_micros.map(format_timestamp_micros),
publish_ready: row.publish_ready,
}
}
fn map_square_hole_run_snapshot(snapshot: SquareHoleRunSnapshot) -> SquareHoleRunRecord {
SquareHoleRunRecord {
run_id: snapshot.run_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
status: normalize_square_hole_run_status(&snapshot.status).to_string(),
snapshot_version: snapshot.snapshot_version,
started_at_ms: i64_to_u64_ms(snapshot.started_at_ms),
duration_limit_ms: i64_to_u64_ms(snapshot.duration_limit_ms),
server_now_ms: Some(i64_to_u64_ms(snapshot.server_now_ms)),
remaining_ms: i64_to_u64_ms(snapshot.remaining_ms),
total_shape_count: snapshot.total_shape_count,
completed_shape_count: snapshot.completed_shape_count,
combo: snapshot.combo,
best_combo: snapshot.best_combo,
score: snapshot.score,
rule_label: snapshot.rule_label,
background_image_src: empty_string_to_none(snapshot.background_image_src),
current_shape: snapshot.current_shape.map(map_square_hole_shape_snapshot),
holes: snapshot
.holes
.into_iter()
.map(map_square_hole_hole_snapshot)
.collect(),
last_feedback: snapshot
.last_feedback
.map(map_square_hole_feedback_snapshot),
last_confirmed_action_id: None,
}
}
fn map_square_hole_shape_snapshot(
snapshot: SquareHoleShapeSnapshot,
) -> SquareHoleShapeSnapshotRecord {
SquareHoleShapeSnapshotRecord {
shape_id: snapshot.shape_id,
shape_kind: snapshot.shape_kind,
label: snapshot.label,
target_hole_id: snapshot.target_hole_id,
color: snapshot.color,
image_src: empty_string_to_none(snapshot.image_src),
}
}
fn map_square_hole_hole_snapshot(snapshot: SquareHoleHoleSnapshot) -> SquareHoleHoleSnapshotRecord {
SquareHoleHoleSnapshotRecord {
hole_id: snapshot.hole_id,
hole_kind: snapshot.hole_kind,
label: snapshot.label,
x: snapshot.x,
y: snapshot.y,
image_src: empty_string_to_none(snapshot.image_src),
}
}
fn map_square_hole_shape_option(
snapshot: SquareHoleShapeOptionSnapshot,
) -> SquareHoleShapeOptionRecord {
SquareHoleShapeOptionRecord {
option_id: snapshot.option_id,
shape_kind: snapshot.shape_kind,
label: snapshot.label,
target_hole_id: snapshot.target_hole_id,
image_prompt: snapshot.image_prompt,
image_src: empty_string_to_none(snapshot.image_src),
}
}
fn map_square_hole_hole_option(
snapshot: SquareHoleHoleOptionSnapshot,
) -> SquareHoleHoleOptionRecord {
SquareHoleHoleOptionRecord {
hole_id: snapshot.hole_id,
hole_kind: snapshot.hole_kind,
label: snapshot.label,
image_prompt: snapshot.image_prompt,
image_src: empty_string_to_none(snapshot.image_src),
}
}
fn map_square_hole_feedback_snapshot(
snapshot: SquareHoleDropFeedbackSnapshot,
) -> SquareHoleDropFeedbackRecord {
SquareHoleDropFeedbackRecord {
accepted: snapshot.accepted,
reject_reason: snapshot
.reject_reason
.map(|value| normalize_square_hole_reject_reason(&value).to_string()),
message: snapshot.message,
}
}
fn build_square_hole_anchor_pack(
config: &SquareHoleCreatorConfigRecord,
) -> SquareHoleAnchorPackRecord {
let shape_count = config.shape_count.to_string();
let difficulty = config.difficulty.to_string();
SquareHoleAnchorPackRecord {
theme: build_square_hole_anchor_item("theme", "题材主题", config.theme_text.as_str()),
twist_rule: build_square_hole_anchor_item(
"twistRule",
"反差规则",
config.twist_rule.as_str(),
),
shape_count: build_square_hole_anchor_item("shapeCount", "形状数量", shape_count.as_str()),
difficulty: build_square_hole_anchor_item("difficulty", "难度", difficulty.as_str()),
}
}
fn build_square_hole_anchor_item(
key: &str,
label: &str,
value: &str,
) -> SquareHoleAnchorItemRecord {
SquareHoleAnchorItemRecord {
key: key.to_string(),
label: label.to_string(),
value: value.to_string(),
status: if value.trim().is_empty() {
"missing"
} else {
"confirmed"
}
.to_string(),
}
}
fn normalize_square_hole_stage(value: &str) -> &str {
match value {
"Collecting" | "CollectingConfig" | "collecting" | "collecting_config" => {
"collecting_config"
}
"ReadyToCompile" | "ready_to_compile" => "ready_to_compile",
"DraftCompiled" | "DraftReady" | "draft_compiled" | "draft_ready" => "draft_ready",
"Published" | "published" => "published",
_ => value,
}
}
fn normalize_square_hole_publication_status(value: &str) -> &str {
match value {
"Draft" | "draft" => "draft",
"Published" | "published" => "published",
_ => value,
}
}
fn normalize_square_hole_run_status(value: &str) -> &str {
match value {
"Running" | "running" => "running",
"Won" | "won" => "won",
"Failed" | "failed" => "failed",
"Stopped" | "stopped" => "stopped",
_ => value,
}
}
fn normalize_square_hole_message_kind(value: &str) -> &str {
match value {
"text" => "chat",
_ => value,
}
}
fn normalize_square_hole_reject_reason(value: &str) -> &str {
match value {
"RunNotActive" | "run_not_active" => "run_not_active",
"SnapshotVersionMismatch" | "snapshot_version_mismatch" => "snapshot_version_mismatch",
"HoleNotFound" | "hole_not_found" => "hole_not_found",
"Incompatible" | "incompatible" => "incompatible",
"TimeUp" | "time_up" => "time_up",
_ => value,
}
}

View File

@@ -0,0 +1,291 @@
use super::*;
impl From<module_runtime::RuntimeSnapshotUpsertInput> for RuntimeSnapshotUpsertInput {
fn from(input: module_runtime::RuntimeSnapshotUpsertInput) -> Self {
Self {
user_id: input.user_id,
saved_at_micros: input.saved_at_micros,
bottom_tab: input.bottom_tab,
game_state_json: input.game_state_json,
current_story_json: input.current_story_json,
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<DomainStorySessionInput> for StorySessionInput {
fn from(input: DomainStorySessionInput) -> Self {
Self {
story_session_id: input.story_session_id,
runtime_session_id: input.runtime_session_id,
actor_user_id: input.actor_user_id,
world_profile_id: input.world_profile_id,
initial_prompt: input.initial_prompt,
opening_summary: input.opening_summary,
created_at_micros: input.created_at_micros,
}
}
}
impl From<DomainStoryContinueInput> for StoryContinueInput {
fn from(input: DomainStoryContinueInput) -> Self {
Self {
story_session_id: input.story_session_id,
event_id: input.event_id,
narrative_text: input.narrative_text,
choice_function_id: input.choice_function_id,
updated_at_micros: input.updated_at_micros,
}
}
}
impl From<DomainStorySessionStateInput> for StorySessionStateInput {
fn from(input: DomainStorySessionStateInput) -> Self {
Self {
story_session_id: input.story_session_id,
}
}
}
pub(crate) fn map_asset_history_list_result(
result: AssetHistoryListResult,
) -> Result<Vec<AssetHistoryEntryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.entries
.into_iter()
.map(map_asset_history_entry_snapshot)
.map(build_asset_history_entry_record)
.collect())
}
pub(crate) fn map_runtime_browse_history_procedure_result(
result: RuntimeBrowseHistoryProcedureResult,
) -> Result<Vec<RuntimeBrowseHistoryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.entries
.into_iter()
.map(|snapshot| {
build_runtime_browse_history_record(map_runtime_browse_history_snapshot(snapshot))
})
.collect())
}
pub(crate) fn map_story_session_procedure_result(
result: StorySessionProcedureResult,
) -> Result<StorySessionResultRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("story session 快照"))?;
let event = result
.event
.ok_or_else(|| SpacetimeClientError::missing_snapshot("story event 快照"))?;
Ok(StorySessionResultRecord {
session: map_story_session_snapshot(session),
event: map_story_event_snapshot(event),
})
}
pub(crate) fn map_story_session_state_procedure_result(
result: StorySessionStateProcedureResult,
) -> Result<StorySessionStateRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("story session state 快照"))?;
Ok(StorySessionStateRecord {
session: map_story_session_snapshot(session),
events: result
.events
.into_iter()
.map(map_story_event_snapshot)
.collect(),
})
}
pub(crate) fn map_asset_history_entry_snapshot(
snapshot: AssetHistoryEntrySnapshot,
) -> module_assets::AssetHistoryEntrySnapshot {
module_assets::AssetHistoryEntrySnapshot {
asset_object_id: snapshot.asset_object_id,
asset_kind: snapshot.asset_kind,
image_src: snapshot.image_src,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
entity_id: snapshot.entity_id,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_runtime_browse_history_snapshot(
snapshot: RuntimeBrowseHistorySnapshot,
) -> module_runtime::RuntimeBrowseHistorySnapshot {
module_runtime::RuntimeBrowseHistorySnapshot {
browse_history_id: snapshot.browse_history_id,
user_id: snapshot.user_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
cover_image_src: snapshot.cover_image_src,
theme_mode: map_runtime_browse_history_theme_mode_back(snapshot.theme_mode),
author_display_name: snapshot.author_display_name,
visited_at_micros: snapshot.visited_at_micros,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_runtime_snapshot_snapshot(
snapshot: RuntimeSnapshot,
) -> module_runtime::RuntimeSnapshot {
module_runtime::RuntimeSnapshot {
user_id: snapshot.user_id,
version: snapshot.version,
saved_at_micros: snapshot.saved_at_micros,
bottom_tab: snapshot.bottom_tab,
game_state_json: snapshot.game_state_json,
current_story_json: snapshot.current_story_json,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_runtime_profile_save_archive_snapshot(
snapshot: RuntimeProfileSaveArchiveSnapshot,
) -> module_runtime::RuntimeProfileSaveArchiveSnapshot {
module_runtime::RuntimeProfileSaveArchiveSnapshot {
archive_id: snapshot.archive_id,
user_id: snapshot.user_id,
world_key: snapshot.world_key,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
world_type: snapshot.world_type,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
cover_image_src: snapshot.cover_image_src,
saved_at_micros: snapshot.saved_at_micros,
bottom_tab: snapshot.bottom_tab,
game_state_json: snapshot.game_state_json,
current_story_json: snapshot.current_story_json,
created_at_micros: snapshot.created_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn map_story_session_snapshot(snapshot: StorySessionSnapshot) -> StorySessionRecord {
StorySessionRecord {
story_session_id: snapshot.story_session_id,
runtime_session_id: snapshot.runtime_session_id,
actor_user_id: snapshot.actor_user_id,
world_profile_id: snapshot.world_profile_id,
initial_prompt: snapshot.initial_prompt,
opening_summary: snapshot.opening_summary,
latest_narrative_text: snapshot.latest_narrative_text,
latest_choice_function_id: snapshot.latest_choice_function_id,
status: map_story_session_status(snapshot.status)
.as_str()
.to_string(),
version: snapshot.version,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
pub(crate) fn map_story_event_snapshot(snapshot: StoryEventSnapshot) -> StoryEventRecord {
StoryEventRecord {
event_id: snapshot.event_id,
story_session_id: snapshot.story_session_id,
event_kind: map_story_event_kind(snapshot.event_kind)
.as_str()
.to_string(),
narrative_text: snapshot.narrative_text,
choice_function_id: snapshot.choice_function_id,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
pub(crate) fn map_runtime_browse_history_theme_mode_back(
value: crate::module_bindings::RuntimeBrowseHistoryThemeMode,
) -> module_runtime::RuntimeBrowseHistoryThemeMode {
match value {
crate::module_bindings::RuntimeBrowseHistoryThemeMode::Martial => {
module_runtime::RuntimeBrowseHistoryThemeMode::Martial
}
crate::module_bindings::RuntimeBrowseHistoryThemeMode::Arcane => {
module_runtime::RuntimeBrowseHistoryThemeMode::Arcane
}
crate::module_bindings::RuntimeBrowseHistoryThemeMode::Machina => {
module_runtime::RuntimeBrowseHistoryThemeMode::Machina
}
crate::module_bindings::RuntimeBrowseHistoryThemeMode::Tide => {
module_runtime::RuntimeBrowseHistoryThemeMode::Tide
}
crate::module_bindings::RuntimeBrowseHistoryThemeMode::Rift => {
module_runtime::RuntimeBrowseHistoryThemeMode::Rift
}
crate::module_bindings::RuntimeBrowseHistoryThemeMode::Mythic => {
module_runtime::RuntimeBrowseHistoryThemeMode::Mythic
}
}
}
pub(crate) fn map_story_session_status(value: StorySessionStatus) -> DomainStorySessionStatus {
match value {
StorySessionStatus::Active => DomainStorySessionStatus::Active,
StorySessionStatus::Completed => DomainStorySessionStatus::Completed,
StorySessionStatus::Archived => DomainStorySessionStatus::Archived,
}
}
pub(crate) fn map_story_event_kind(value: StoryEventKind) -> DomainStoryEventKind {
match value {
StoryEventKind::SessionStarted => DomainStoryEventKind::SessionStarted,
StoryEventKind::StoryContinued => DomainStoryEventKind::StoryContinued,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VisualNovelRuntimeEventRecordInput {
pub event_id: String,
pub run_id: String,
pub owner_user_id: String,
pub profile_id: Option<String>,
pub event_kind: String,
pub client_event_id: Option<String>,
pub history_entry_id: Option<String>,
pub payload_json: String,
pub occurred_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VisualNovelRuntimeEventRecord {
pub event_id: String,
pub run_id: Option<String>,
pub owner_user_id: String,
pub profile_id: Option<String>,
pub event_kind: String,
pub client_event_id: Option<String>,
pub history_entry_id: Option<String>,
pub payload: serde_json::Value,
pub occurred_at: String,
}

View File

@@ -0,0 +1,252 @@
use super::*;
pub(crate) fn map_visual_novel_agent_session_procedure_result(
result: VisualNovelAgentSessionProcedureResult,
) -> Result<VisualNovelAgentSessionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session = result
.session
.ok_or_else(|| SpacetimeClientError::missing_snapshot("visual novel agent session 快照"))?;
Ok(map_visual_novel_agent_session_snapshot(session))
}
pub(crate) fn map_visual_novel_work_procedure_result(
result: VisualNovelWorkProcedureResult,
) -> Result<VisualNovelWorkProfileRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let work = result
.work
.ok_or_else(|| SpacetimeClientError::missing_snapshot("visual novel work 快照"))?;
Ok(map_visual_novel_work_snapshot(work))
}
pub(crate) fn map_visual_novel_works_procedure_result(
result: VisualNovelWorksProcedureResult,
) -> Result<Vec<VisualNovelWorkProfileRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.items
.into_iter()
.map(map_visual_novel_work_snapshot)
.collect())
}
pub(crate) fn map_visual_novel_run_procedure_result(
result: VisualNovelRunProcedureResult,
) -> Result<VisualNovelRunRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run = result
.run
.ok_or_else(|| SpacetimeClientError::missing_snapshot("visual novel run 快照"))?;
Ok(map_visual_novel_run_snapshot(run))
}
pub(crate) fn map_visual_novel_history_procedure_result(
result: VisualNovelHistoryProcedureResult,
) -> Result<Vec<VisualNovelHistoryEntryRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.items
.into_iter()
.map(map_visual_novel_history_entry)
.collect())
}
pub(crate) fn map_visual_novel_runtime_event_procedure_result(
result: VisualNovelRuntimeEventProcedureResult,
) -> Result<VisualNovelRuntimeEventRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let event = result
.event
.ok_or_else(|| SpacetimeClientError::missing_snapshot("visual novel runtime event 快照"))?;
Ok(map_visual_novel_runtime_event(event))
}
fn map_visual_novel_agent_session_snapshot(
snapshot: VisualNovelAgentSessionSnapshot,
) -> VisualNovelAgentSessionRecord {
VisualNovelAgentSessionRecord {
session_id: snapshot.session_id,
owner_user_id: snapshot.owner_user_id,
source_mode: snapshot.source_mode,
status: snapshot.status,
seed_text: snapshot.seed_text,
source_asset_ids: snapshot.source_asset_ids,
current_turn: snapshot.current_turn,
progress_percent: snapshot.progress_percent,
messages: snapshot
.messages
.into_iter()
.map(map_visual_novel_agent_message)
.collect(),
draft: snapshot.draft.map(visual_novel_json_to_value),
pending_action: snapshot.pending_action.map(visual_novel_json_to_value),
last_assistant_reply: snapshot.last_assistant_reply,
published_profile_id: snapshot.published_profile_id,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_visual_novel_agent_message(
snapshot: VisualNovelAgentMessageSnapshot,
) -> VisualNovelAgentMessageRecord {
VisualNovelAgentMessageRecord {
message_id: snapshot.message_id,
session_id: snapshot.session_id,
role: snapshot.role,
kind: snapshot.kind,
text: snapshot.text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
fn map_visual_novel_work_snapshot(
snapshot: VisualNovelWorkSnapshot,
) -> VisualNovelWorkProfileRecord {
VisualNovelWorkProfileRecord {
work_id: snapshot.work_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
source_session_id: snapshot.source_session_id,
author_display_name: snapshot.author_display_name,
work_title: snapshot.work_title,
work_description: snapshot.work_description,
tags: snapshot.tags,
cover_image_src: snapshot.cover_image_src,
source_asset_ids: snapshot.source_asset_ids,
draft: visual_novel_json_to_value(snapshot.draft),
publication_status: snapshot.publication_status,
publish_ready: snapshot.publish_ready,
play_count: snapshot.play_count,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
}
}
pub(crate) fn map_visual_novel_gallery_view_row(
row: VisualNovelGalleryViewRow,
) -> VisualNovelWorkProfileRecord {
VisualNovelWorkProfileRecord {
work_id: row.work_id,
profile_id: row.profile_id,
owner_user_id: row.owner_user_id,
source_session_id: row.source_session_id,
author_display_name: row.author_display_name,
work_title: row.work_title,
work_description: row.work_description,
tags: row.tags,
cover_image_src: row.cover_image_src,
source_asset_ids: row.source_asset_ids,
// 中文注释:公开列表 view 不暴露完整 draft详情页仍通过 detail procedure 读取。
draft: serde_json::Value::Null,
publication_status: row.publication_status,
publish_ready: row.publish_ready,
play_count: row.play_count,
created_at: format_timestamp_micros(row.created_at_micros),
updated_at: format_timestamp_micros(row.updated_at_micros),
published_at: row.published_at_micros.map(format_timestamp_micros),
}
}
fn map_visual_novel_run_snapshot(snapshot: VisualNovelRunSnapshot) -> VisualNovelRunRecord {
VisualNovelRunRecord {
run_id: snapshot.run_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
mode: snapshot.mode,
status: snapshot.status,
current_scene_id: snapshot.current_scene_id,
current_phase_id: snapshot.current_phase_id,
visible_character_ids: snapshot.visible_character_ids,
flags: visual_novel_json_to_value(snapshot.flags),
metrics: visual_novel_json_to_value(snapshot.metrics),
history: snapshot
.history
.into_iter()
.map(map_visual_novel_history_entry)
.collect(),
available_choices: visual_novel_json_to_value(snapshot.available_choices),
text_mode_enabled: snapshot.text_mode_enabled,
created_at: format_timestamp_micros(snapshot.created_at_micros),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_visual_novel_history_entry(
snapshot: VisualNovelRuntimeHistoryEntrySnapshot,
) -> VisualNovelHistoryEntryRecord {
VisualNovelHistoryEntryRecord {
entry_id: snapshot.entry_id,
run_id: snapshot.run_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
turn_index: snapshot.turn_index,
source: snapshot.source,
action_text: snapshot.action_text,
steps: visual_novel_json_to_value(snapshot.steps),
snapshot_before_hash: snapshot.snapshot_before_hash,
snapshot_after_hash: snapshot.snapshot_after_hash,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
fn map_visual_novel_runtime_event(
snapshot: VisualNovelRuntimeEventSnapshot,
) -> VisualNovelRuntimeEventRecord {
VisualNovelRuntimeEventRecord {
event_id: snapshot.event_id,
run_id: snapshot.run_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
event_kind: snapshot.event_kind,
client_event_id: snapshot.client_event_id,
history_entry_id: snapshot.history_entry_id,
payload: visual_novel_json_to_value(snapshot.payload),
occurred_at: format_timestamp_micros(snapshot.occurred_at_micros),
}
}
fn visual_novel_json_to_value(value: VisualNovelJsonValue) -> serde_json::Value {
match value {
VisualNovelJsonValue::Null => serde_json::Value::Null,
VisualNovelJsonValue::Bool(value) => serde_json::Value::Bool(value),
VisualNovelJsonValue::Number(value) => serde_json::Number::from_f64(value)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null),
VisualNovelJsonValue::String(value) => serde_json::Value::String(value),
VisualNovelJsonValue::Array(items) => {
serde_json::Value::Array(items.into_iter().map(visual_novel_json_to_value).collect())
}
VisualNovelJsonValue::Object(fields) => {
let object = fields
.into_iter()
.map(|field| (field.key, visual_novel_json_to_value(field.value)))
.collect();
serde_json::Value::Object(object)
}
}
}