Replace uses of the legacy `gpt-image-2-all` model with `gpt-image-2` and standardize image workflows: no-reference generation uses POST /v1/images/generations, any-reference flows use POST /v1/images/edits with multipart `image` parts. Update SKILLs, generation scripts, decision logs, and docs to reflect the contract change and edits-vs-generations guidance. Apply corresponding changes across backend (api-server match3d/puzzle modules, openai image adapter, mappers, telemetry, spacetime client/module), frontend components and services (Match3D, Puzzle, CreativeImageInputPanel, runtime shells), and add new spritesheet/parser files and tests. Also add media/logo.png. These changes align repository code and documentation with the VectorEngine image API contract and update generation/upload handling (green-screen -> alpha processing, spritesheet handling, and related tests).
665 lines
23 KiB
Rust
665 lines
23 KiB
Rust
use super::*;
|
|
|
|
pub(super) fn map_puzzle_agent_session_response(
|
|
session: PuzzleAgentSessionRecord,
|
|
) -> PuzzleAgentSessionSnapshotResponse {
|
|
PuzzleAgentSessionSnapshotResponse {
|
|
session_id: session.session_id,
|
|
seed_text: session.seed_text,
|
|
current_turn: session.current_turn,
|
|
progress_percent: session.progress_percent,
|
|
stage: session.stage,
|
|
anchor_pack: map_puzzle_anchor_pack_response(session.anchor_pack),
|
|
draft: session.draft.map(map_puzzle_result_draft_response),
|
|
messages: session
|
|
.messages
|
|
.into_iter()
|
|
.map(map_puzzle_agent_message_response)
|
|
.collect(),
|
|
last_assistant_reply: session.last_assistant_reply,
|
|
published_profile_id: session.published_profile_id,
|
|
suggested_actions: session
|
|
.suggested_actions
|
|
.into_iter()
|
|
.map(map_puzzle_suggested_action_response)
|
|
.collect(),
|
|
result_preview: session
|
|
.result_preview
|
|
.map(map_puzzle_result_preview_response),
|
|
updated_at: session.updated_at,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_anchor_pack_response(
|
|
anchor_pack: PuzzleAnchorPackRecord,
|
|
) -> PuzzleAnchorPackResponse {
|
|
PuzzleAnchorPackResponse {
|
|
theme_promise: map_puzzle_anchor_item_response(anchor_pack.theme_promise),
|
|
visual_subject: map_puzzle_anchor_item_response(anchor_pack.visual_subject),
|
|
visual_mood: map_puzzle_anchor_item_response(anchor_pack.visual_mood),
|
|
composition_hooks: map_puzzle_anchor_item_response(anchor_pack.composition_hooks),
|
|
tags_and_forbidden: map_puzzle_anchor_item_response(anchor_pack.tags_and_forbidden),
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_anchor_item_response(
|
|
anchor: PuzzleAnchorItemRecord,
|
|
) -> PuzzleAnchorItemResponse {
|
|
PuzzleAnchorItemResponse {
|
|
key: anchor.key,
|
|
label: anchor.label,
|
|
value: anchor.value,
|
|
status: anchor.status,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_result_draft_response(
|
|
draft: PuzzleResultDraftRecord,
|
|
) -> PuzzleResultDraftResponse {
|
|
PuzzleResultDraftResponse {
|
|
work_title: draft.work_title,
|
|
work_description: draft.work_description,
|
|
level_name: draft.level_name,
|
|
summary: draft.summary,
|
|
theme_tags: draft.theme_tags,
|
|
forbidden_directives: draft.forbidden_directives,
|
|
creator_intent: draft.creator_intent.map(map_puzzle_creator_intent_response),
|
|
anchor_pack: map_puzzle_anchor_pack_response(draft.anchor_pack),
|
|
candidates: draft
|
|
.candidates
|
|
.into_iter()
|
|
.map(map_puzzle_generated_image_candidate_response)
|
|
.collect(),
|
|
selected_candidate_id: draft.selected_candidate_id,
|
|
cover_image_src: draft.cover_image_src,
|
|
cover_asset_id: draft.cover_asset_id,
|
|
generation_status: draft.generation_status,
|
|
levels: draft
|
|
.levels
|
|
.into_iter()
|
|
.map(map_puzzle_draft_level_response)
|
|
.collect(),
|
|
form_draft: draft.form_draft.map(map_puzzle_form_draft_response),
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_form_draft_response(
|
|
draft: PuzzleFormDraftRecord,
|
|
) -> PuzzleFormDraftResponse {
|
|
PuzzleFormDraftResponse {
|
|
work_title: draft.work_title,
|
|
work_description: draft.work_description,
|
|
picture_description: draft.picture_description,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_draft_level_response(
|
|
level: PuzzleDraftLevelRecord,
|
|
) -> PuzzleDraftLevelResponse {
|
|
let generation_status = resolve_puzzle_level_generation_status(&level);
|
|
PuzzleDraftLevelResponse {
|
|
level_id: level.level_id,
|
|
level_name: level.level_name,
|
|
picture_description: level.picture_description,
|
|
picture_reference: level.picture_reference,
|
|
ui_background_prompt: level.ui_background_prompt,
|
|
ui_background_image_src: level.ui_background_image_src,
|
|
ui_background_image_object_key: level.ui_background_image_object_key,
|
|
level_scene_image_src: level.level_scene_image_src,
|
|
level_scene_image_object_key: level.level_scene_image_object_key,
|
|
ui_spritesheet_image_src: level.ui_spritesheet_image_src,
|
|
ui_spritesheet_image_object_key: level.ui_spritesheet_image_object_key,
|
|
level_background_image_src: level.level_background_image_src,
|
|
level_background_image_object_key: level.level_background_image_object_key,
|
|
background_music: level
|
|
.background_music
|
|
.map(map_puzzle_audio_asset_record_response),
|
|
candidates: level
|
|
.candidates
|
|
.into_iter()
|
|
.map(map_puzzle_generated_image_candidate_response)
|
|
.collect(),
|
|
selected_candidate_id: level.selected_candidate_id,
|
|
cover_image_src: level.cover_image_src,
|
|
cover_asset_id: level.cover_asset_id,
|
|
generation_status,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_audio_asset_record_response(
|
|
asset: PuzzleAudioAssetRecord,
|
|
) -> CreationAudioAsset {
|
|
CreationAudioAsset {
|
|
task_id: asset.task_id,
|
|
provider: asset.provider,
|
|
asset_object_id: asset.asset_object_id,
|
|
asset_kind: asset.asset_kind,
|
|
audio_src: asset.audio_src,
|
|
prompt: asset.prompt,
|
|
title: asset.title,
|
|
updated_at: asset.updated_at,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_audio_asset_domain_record(
|
|
asset: module_puzzle::PuzzleAudioAsset,
|
|
) -> PuzzleAudioAssetRecord {
|
|
PuzzleAudioAssetRecord {
|
|
task_id: asset.task_id,
|
|
provider: asset.provider,
|
|
asset_object_id: asset.asset_object_id,
|
|
asset_kind: asset.asset_kind,
|
|
audio_src: asset.audio_src,
|
|
prompt: asset.prompt,
|
|
title: asset.title,
|
|
updated_at: asset.updated_at,
|
|
}
|
|
}
|
|
|
|
pub(super) fn puzzle_audio_asset_response_module_json(asset: &Option<CreationAudioAsset>) -> Value {
|
|
asset
|
|
.as_ref()
|
|
.map(|asset| {
|
|
json!({
|
|
"task_id": asset.task_id,
|
|
"provider": asset.provider,
|
|
"asset_object_id": asset.asset_object_id,
|
|
"asset_kind": asset.asset_kind,
|
|
"audio_src": asset.audio_src,
|
|
"prompt": asset.prompt,
|
|
"title": asset.title,
|
|
"updated_at": asset.updated_at,
|
|
})
|
|
})
|
|
.unwrap_or(Value::Null)
|
|
}
|
|
|
|
pub(super) fn puzzle_audio_asset_record_module_json(
|
|
asset: &Option<PuzzleAudioAssetRecord>,
|
|
) -> Value {
|
|
asset
|
|
.as_ref()
|
|
.map(|asset| {
|
|
json!({
|
|
"task_id": asset.task_id,
|
|
"provider": asset.provider,
|
|
"asset_object_id": asset.asset_object_id,
|
|
"asset_kind": asset.asset_kind,
|
|
"audio_src": asset.audio_src,
|
|
"prompt": asset.prompt,
|
|
"title": asset.title,
|
|
"updated_at": asset.updated_at,
|
|
})
|
|
})
|
|
.unwrap_or(Value::Null)
|
|
}
|
|
|
|
pub(super) fn map_puzzle_creator_intent_response(
|
|
intent: PuzzleCreatorIntentRecord,
|
|
) -> PuzzleCreatorIntentResponse {
|
|
PuzzleCreatorIntentResponse {
|
|
source_mode: intent.source_mode,
|
|
raw_messages_summary: intent.raw_messages_summary,
|
|
theme_promise: intent.theme_promise,
|
|
visual_subject: intent.visual_subject,
|
|
visual_mood: intent.visual_mood,
|
|
composition_hooks: intent.composition_hooks,
|
|
theme_tags: intent.theme_tags,
|
|
forbidden_directives: intent.forbidden_directives,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_generated_image_candidate_response(
|
|
candidate: PuzzleGeneratedImageCandidateRecord,
|
|
) -> PuzzleGeneratedImageCandidateResponse {
|
|
PuzzleGeneratedImageCandidateResponse {
|
|
candidate_id: candidate.candidate_id,
|
|
image_src: candidate.image_src,
|
|
asset_id: candidate.asset_id,
|
|
prompt: candidate.prompt,
|
|
actual_prompt: candidate.actual_prompt,
|
|
source_type: candidate.source_type,
|
|
selected: candidate.selected,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_agent_message_response(
|
|
message: PuzzleAgentMessageRecord,
|
|
) -> PuzzleAgentMessageResponse {
|
|
PuzzleAgentMessageResponse {
|
|
id: message.message_id,
|
|
role: message.role,
|
|
kind: message.kind,
|
|
text: message.text,
|
|
created_at: message.created_at,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_suggested_action_response(
|
|
action: PuzzleAgentSuggestedActionRecord,
|
|
) -> PuzzleAgentSuggestedActionResponse {
|
|
PuzzleAgentSuggestedActionResponse {
|
|
id: action.action_id,
|
|
action_type: action.action_type,
|
|
label: action.label,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_result_preview_response(
|
|
preview: PuzzleResultPreviewRecord,
|
|
) -> PuzzleResultPreviewEnvelopeResponse {
|
|
PuzzleResultPreviewEnvelopeResponse {
|
|
draft: map_puzzle_result_draft_response(preview.draft),
|
|
blockers: preview
|
|
.blockers
|
|
.into_iter()
|
|
.map(map_puzzle_result_preview_blocker_response)
|
|
.collect(),
|
|
quality_findings: preview
|
|
.quality_findings
|
|
.into_iter()
|
|
.map(map_puzzle_result_preview_finding_response)
|
|
.collect(),
|
|
publish_ready: preview.publish_ready,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_result_preview_blocker_response(
|
|
blocker: PuzzleResultPreviewBlockerRecord,
|
|
) -> PuzzleResultPreviewBlockerResponse {
|
|
PuzzleResultPreviewBlockerResponse {
|
|
id: blocker.blocker_id,
|
|
code: blocker.code,
|
|
message: blocker.message,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_result_preview_finding_response(
|
|
finding: PuzzleResultPreviewFindingRecord,
|
|
) -> PuzzleResultPreviewFindingResponse {
|
|
PuzzleResultPreviewFindingResponse {
|
|
id: finding.finding_id,
|
|
severity: finding.severity,
|
|
code: finding.code,
|
|
message: finding.message,
|
|
}
|
|
}
|
|
|
|
fn resolve_puzzle_work_generation_status(item: &PuzzleWorkProfileRecord) -> Option<String> {
|
|
let has_viewable_result = item
|
|
.cover_image_src
|
|
.as_deref()
|
|
.map(str::trim)
|
|
.is_some_and(|value| !value.is_empty())
|
|
|| item.levels.iter().any(has_puzzle_level_image);
|
|
if has_viewable_result {
|
|
return Some("ready".to_string());
|
|
}
|
|
|
|
item.levels
|
|
.iter()
|
|
.map(resolve_puzzle_level_generation_status)
|
|
.find(|status| status.as_str() == "generating")
|
|
.or_else(|| {
|
|
item.levels
|
|
.iter()
|
|
.map(resolve_puzzle_level_generation_status)
|
|
.find(|status| status.as_str() == "ready")
|
|
})
|
|
.or_else(|| {
|
|
item.levels
|
|
.iter()
|
|
.map(resolve_puzzle_level_generation_status)
|
|
.find(|status| !status.is_empty())
|
|
})
|
|
}
|
|
|
|
fn resolve_puzzle_level_generation_status(level: &PuzzleDraftLevelRecord) -> String {
|
|
if level.generation_status.trim() == "generating" && has_puzzle_level_image(level) {
|
|
return "ready".to_string();
|
|
}
|
|
|
|
level.generation_status.trim().to_string()
|
|
}
|
|
|
|
fn has_puzzle_level_image(level: &PuzzleDraftLevelRecord) -> bool {
|
|
let has_cover = level
|
|
.cover_image_src
|
|
.as_deref()
|
|
.map(str::trim)
|
|
.is_some_and(|value| !value.is_empty());
|
|
let has_selected_candidate = level
|
|
.selected_candidate_id
|
|
.as_deref()
|
|
.and_then(|candidate_id| {
|
|
level
|
|
.candidates
|
|
.iter()
|
|
.find(|candidate| candidate.candidate_id == candidate_id)
|
|
})
|
|
.map(|candidate| candidate.image_src.trim())
|
|
.is_some_and(|value| !value.is_empty());
|
|
let has_fallback_candidate = level
|
|
.candidates
|
|
.last()
|
|
.map(|candidate| candidate.image_src.trim())
|
|
.is_some_and(|value| !value.is_empty());
|
|
|
|
has_cover || has_selected_candidate || has_fallback_candidate
|
|
}
|
|
|
|
pub(super) fn map_puzzle_work_summary_response(
|
|
state: &PuzzleApiState,
|
|
item: PuzzleWorkProfileRecord,
|
|
) -> PuzzleWorkSummaryResponse {
|
|
let generation_status = resolve_puzzle_work_generation_status(&item);
|
|
let author = resolve_puzzle_work_author_by_user_id(
|
|
state,
|
|
&item.owner_user_id,
|
|
Some(&item.author_display_name),
|
|
None,
|
|
);
|
|
PuzzleWorkSummaryResponse {
|
|
work_id: item.work_id,
|
|
profile_id: item.profile_id,
|
|
owner_user_id: item.owner_user_id,
|
|
source_session_id: item.source_session_id,
|
|
author_display_name: author.display_name,
|
|
work_title: item.work_title,
|
|
work_description: item.work_description,
|
|
level_name: item.level_name,
|
|
summary: item.summary,
|
|
theme_tags: item.theme_tags,
|
|
cover_image_src: item.cover_image_src,
|
|
cover_asset_id: item.cover_asset_id,
|
|
publication_status: item.publication_status,
|
|
updated_at: item.updated_at,
|
|
published_at: item.published_at,
|
|
play_count: item.play_count,
|
|
remix_count: item.remix_count,
|
|
like_count: item.like_count,
|
|
recent_play_count_7d: item.recent_play_count_7d,
|
|
point_incentive_total_half_points: item.point_incentive_total_half_points,
|
|
point_incentive_claimed_points: item.point_incentive_claimed_points,
|
|
point_incentive_total_points: item.point_incentive_total_half_points as f64 / 2.0,
|
|
point_incentive_claimable_points: item
|
|
.point_incentive_total_half_points
|
|
.saturating_div(2)
|
|
.saturating_sub(item.point_incentive_claimed_points),
|
|
publish_ready: item.publish_ready,
|
|
generation_status,
|
|
levels: item
|
|
.levels
|
|
.iter()
|
|
.map(|x| map_puzzle_draft_level_response(x.clone()))
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_gallery_card_response(
|
|
state: &PuzzleApiState,
|
|
item: PuzzleGalleryCardRecord,
|
|
) -> PuzzleWorkSummaryResponse {
|
|
let author = resolve_puzzle_work_author_by_user_id(
|
|
state,
|
|
&item.owner_user_id,
|
|
Some(&item.author_display_name),
|
|
None,
|
|
);
|
|
PuzzleWorkSummaryResponse {
|
|
work_id: item.work_id,
|
|
profile_id: item.profile_id,
|
|
owner_user_id: item.owner_user_id,
|
|
source_session_id: item.source_session_id,
|
|
author_display_name: author.display_name,
|
|
work_title: item.work_title,
|
|
work_description: item.work_description,
|
|
level_name: item.level_name,
|
|
summary: item.summary,
|
|
theme_tags: item.theme_tags,
|
|
cover_image_src: item.cover_image_src,
|
|
cover_asset_id: item.cover_asset_id,
|
|
publication_status: item.publication_status,
|
|
updated_at: item.updated_at,
|
|
published_at: item.published_at,
|
|
play_count: item.play_count,
|
|
remix_count: item.remix_count,
|
|
like_count: item.like_count,
|
|
recent_play_count_7d: item.recent_play_count_7d,
|
|
point_incentive_total_half_points: item.point_incentive_total_half_points,
|
|
point_incentive_claimed_points: item.point_incentive_claimed_points,
|
|
point_incentive_total_points: item.point_incentive_total_half_points as f64 / 2.0,
|
|
point_incentive_claimable_points: item
|
|
.point_incentive_total_half_points
|
|
.saturating_div(2)
|
|
.saturating_sub(item.point_incentive_claimed_points),
|
|
publish_ready: item.publish_ready,
|
|
generation_status: item.generation_status,
|
|
levels: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_work_profile_response(
|
|
state: &PuzzleApiState,
|
|
item: PuzzleWorkProfileRecord,
|
|
) -> PuzzleWorkProfileResponse {
|
|
let mut summary = map_puzzle_work_summary_response(state, item.clone());
|
|
summary.levels = item
|
|
.levels
|
|
.into_iter()
|
|
.map(map_puzzle_draft_level_response)
|
|
.collect();
|
|
|
|
PuzzleWorkProfileResponse {
|
|
summary,
|
|
anchor_pack: map_puzzle_anchor_pack_response(item.anchor_pack),
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_run_response(run: PuzzleRunRecord) -> PuzzleRunSnapshotResponse {
|
|
PuzzleRunSnapshotResponse {
|
|
run_id: run.run_id,
|
|
entry_profile_id: run.entry_profile_id,
|
|
cleared_level_count: run.cleared_level_count,
|
|
current_level_index: run.current_level_index,
|
|
current_grid_size: run.current_grid_size,
|
|
played_profile_ids: run.played_profile_ids,
|
|
previous_level_tags: run.previous_level_tags,
|
|
current_level: run.current_level.map(map_puzzle_runtime_level_response),
|
|
recommended_next_profile_id: run.recommended_next_profile_id,
|
|
next_level_mode: run.next_level_mode,
|
|
next_level_profile_id: run.next_level_profile_id,
|
|
next_level_id: run.next_level_id,
|
|
recommended_next_works: run
|
|
.recommended_next_works
|
|
.into_iter()
|
|
.map(map_puzzle_recommended_next_work_response)
|
|
.collect(),
|
|
leaderboard_entries: run
|
|
.leaderboard_entries
|
|
.into_iter()
|
|
.map(map_puzzle_leaderboard_entry_response)
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_recommended_next_work_response(
|
|
item: PuzzleRecommendedNextWorkRecord,
|
|
) -> PuzzleRecommendedNextWorkResponse {
|
|
PuzzleRecommendedNextWorkResponse {
|
|
profile_id: item.profile_id,
|
|
level_name: item.level_name,
|
|
author_display_name: item.author_display_name,
|
|
theme_tags: item.theme_tags,
|
|
cover_image_src: item.cover_image_src,
|
|
similarity_score: item.similarity_score,
|
|
}
|
|
}
|
|
|
|
pub(super) async fn enrich_puzzle_run_author_name(
|
|
state: &PuzzleApiState,
|
|
mut run: PuzzleRunRecord,
|
|
) -> PuzzleRunRecord {
|
|
if let Some(level) = run.current_level.as_mut() {
|
|
if let Ok(profile) = state
|
|
.spacetime_client()
|
|
.get_puzzle_gallery_detail(level.profile_id.clone())
|
|
.await
|
|
{
|
|
level.author_display_name = resolve_puzzle_work_author_by_user_id(
|
|
state,
|
|
&profile.owner_user_id,
|
|
Some(&profile.author_display_name),
|
|
None,
|
|
)
|
|
.display_name;
|
|
}
|
|
}
|
|
|
|
run
|
|
}
|
|
|
|
pub(super) fn map_puzzle_runtime_level_response(
|
|
level: spacetime_client::PuzzleRuntimeLevelRecord,
|
|
) -> PuzzleRuntimeLevelSnapshotResponse {
|
|
let timer_defaults =
|
|
build_puzzle_runtime_timer_response_defaults(level.level_index, level.grid_size);
|
|
let time_limit_ms = if level.time_limit_ms == 0 {
|
|
timer_defaults.time_limit_ms
|
|
} else {
|
|
level.time_limit_ms
|
|
};
|
|
let remaining_ms =
|
|
if level.remaining_ms == 0 && level.status == PuzzleRuntimeLevelStatus::Playing.as_str() {
|
|
time_limit_ms
|
|
} else {
|
|
level.remaining_ms.min(time_limit_ms)
|
|
};
|
|
PuzzleRuntimeLevelSnapshotResponse {
|
|
run_id: level.run_id,
|
|
level_index: level.level_index,
|
|
level_id: level.level_id,
|
|
grid_size: level.grid_size,
|
|
profile_id: level.profile_id,
|
|
level_name: level.level_name,
|
|
author_display_name: level.author_display_name,
|
|
theme_tags: level.theme_tags,
|
|
cover_image_src: level.cover_image_src,
|
|
ui_background_image_src: level.ui_background_image_src,
|
|
ui_background_image_object_key: level.ui_background_image_object_key,
|
|
level_background_image_src: level.level_background_image_src,
|
|
level_background_image_object_key: level.level_background_image_object_key,
|
|
ui_spritesheet_image_src: level.ui_spritesheet_image_src,
|
|
ui_spritesheet_image_object_key: level.ui_spritesheet_image_object_key,
|
|
background_music: level
|
|
.background_music
|
|
.map(map_puzzle_audio_asset_record_response),
|
|
board: map_puzzle_board_response(level.board),
|
|
status: level.status,
|
|
started_at_ms: level.started_at_ms,
|
|
cleared_at_ms: level.cleared_at_ms,
|
|
elapsed_ms: level.elapsed_ms,
|
|
time_limit_ms,
|
|
remaining_ms,
|
|
paused_accumulated_ms: level.paused_accumulated_ms,
|
|
pause_started_at_ms: level.pause_started_at_ms,
|
|
freeze_accumulated_ms: level.freeze_accumulated_ms,
|
|
freeze_started_at_ms: level.freeze_started_at_ms,
|
|
freeze_until_ms: level.freeze_until_ms,
|
|
leaderboard_entries: level
|
|
.leaderboard_entries
|
|
.into_iter()
|
|
.map(map_puzzle_leaderboard_entry_response)
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
struct PuzzleRuntimeTimerResponseDefaults {
|
|
time_limit_ms: u64,
|
|
}
|
|
|
|
fn build_puzzle_runtime_timer_response_defaults(
|
|
level_index: u32,
|
|
grid_size: u32,
|
|
) -> PuzzleRuntimeTimerResponseDefaults {
|
|
let time_limit_ms = if level_index > 0 {
|
|
module_puzzle::resolve_puzzle_level_time_limit_ms_by_index(level_index)
|
|
} else {
|
|
module_puzzle::resolve_puzzle_level_time_limit_ms(grid_size)
|
|
};
|
|
PuzzleRuntimeTimerResponseDefaults { time_limit_ms }
|
|
}
|
|
|
|
pub(super) fn map_puzzle_leaderboard_entry_response(
|
|
entry: PuzzleLeaderboardEntryRecord,
|
|
) -> PuzzleLeaderboardEntryResponse {
|
|
PuzzleLeaderboardEntryResponse {
|
|
rank: entry.rank,
|
|
nickname: entry.nickname,
|
|
elapsed_ms: entry.elapsed_ms,
|
|
visible_tags: entry.visible_tags,
|
|
is_current_player: entry.is_current_player,
|
|
}
|
|
}
|
|
|
|
pub(super) fn map_puzzle_board_response(
|
|
board: spacetime_client::PuzzleBoardRecord,
|
|
) -> PuzzleBoardSnapshotResponse {
|
|
PuzzleBoardSnapshotResponse {
|
|
rows: board.rows,
|
|
cols: board.cols,
|
|
pieces: board
|
|
.pieces
|
|
.into_iter()
|
|
.map(|piece| PuzzlePieceStateResponse {
|
|
piece_id: piece.piece_id,
|
|
correct_row: piece.correct_row,
|
|
correct_col: piece.correct_col,
|
|
current_row: piece.current_row,
|
|
current_col: piece.current_col,
|
|
merged_group_id: piece.merged_group_id,
|
|
})
|
|
.collect(),
|
|
merged_groups: board
|
|
.merged_groups
|
|
.into_iter()
|
|
.map(|group| PuzzleMergedGroupStateResponse {
|
|
group_id: group.group_id,
|
|
piece_ids: group.piece_ids,
|
|
occupied_cells: group
|
|
.occupied_cells
|
|
.into_iter()
|
|
.map(|cell| PuzzleCellPositionResponse {
|
|
row: cell.row,
|
|
col: cell.col,
|
|
})
|
|
.collect(),
|
|
})
|
|
.collect(),
|
|
selected_piece_id: board.selected_piece_id,
|
|
all_tiles_resolved: board.all_tiles_resolved,
|
|
}
|
|
}
|
|
|
|
pub(super) fn resolve_author_display_name(
|
|
state: &PuzzleApiState,
|
|
authenticated: &AuthenticatedAccessToken,
|
|
) -> String {
|
|
state
|
|
.auth_user_service()
|
|
.get_user_by_id(authenticated.claims().user_id())
|
|
.ok()
|
|
.flatten()
|
|
.map(|user| user.display_name)
|
|
.filter(|value| !value.trim().is_empty())
|
|
.unwrap_or_else(|| "玩家".to_string())
|
|
}
|
|
|
|
pub(super) fn build_puzzle_welcome_text(seed_text: &str) -> String {
|
|
if seed_text.trim().is_empty() {
|
|
return "拼图创作信息已准备好。".to_string();
|
|
}
|
|
|
|
"拼图创作信息已准备好。".to_string()
|
|
}
|