Add generationStatus and match3d/runtime fixes

Introduce persistent generationStatus to work summaries (puzzle & match3d) and propagate generation recovery rules across docs and frontend/backends so "generating" is restored from server-side work summary rather than ephemeral front-end notices. Update API server image/asset handling (improve match3d material sheet green/alpha decontamination and promote generatedItemAssets background fields) and add runtime improvements: alpha-based hotspot hit-testing, tray insertion/three-match animation behavior, and session re-read on client-side VectorEngine timeouts/lock-screen interruptions. Many docs, tests and related frontend modules updated/added to reflect these contract and behavior changes.
This commit is contained in:
2026-05-16 22:59:02 +08:00
parent bb60ca91ef
commit a45e358e83
42 changed files with 3872 additions and 443 deletions

View File

@@ -459,6 +459,11 @@ fn compile_match3d_draft_tx(
config.theme_text.as_str(),
);
let summary_text = resolve_compile_summary_text(&input.summary_text, existing_work.as_ref());
let compiled_at = Timestamp::from_micros_since_unix_epoch(input.compiled_at_micros);
let generated_item_assets_json = resolve_generated_item_assets_json_for_compile(
input.generated_item_assets_json.as_deref(),
existing_work.as_ref(),
)?;
let draft = Match3DDraftSnapshot {
profile_id: input.profile_id.clone(),
game_name: game_name.clone(),
@@ -467,12 +472,9 @@ fn compile_match3d_draft_tx(
tags: tags.clone(),
clear_count: config.clear_count,
difficulty: config.difficulty,
// 中文注释:草稿响应本身也携带生成素材快照,避免 HTTP facade 回读 work 详情失败时丢失背景/容器图。
generated_item_assets_json: generated_item_assets_json.clone(),
};
let compiled_at = Timestamp::from_micros_since_unix_epoch(input.compiled_at_micros);
let generated_item_assets_json = resolve_generated_item_assets_json_for_compile(
input.generated_item_assets_json.as_deref(),
existing_work.as_ref(),
)?;
let previous_publication_status = existing_work
.as_ref()
.map(|work| work.publication_status.clone())
@@ -1889,6 +1891,32 @@ mod tests {
);
}
#[test]
fn match3d_draft_snapshot_keeps_generated_item_assets_json() {
let draft = Match3DDraftSnapshot {
profile_id: "profile-1".to_string(),
game_name: "水果抓大鹅".to_string(),
theme_text: "水果".to_string(),
summary_text: "水果主题".to_string(),
tags: vec!["水果".to_string()],
clear_count: 3,
difficulty: 3,
generated_item_assets_json: Some(
r#"[{"itemId":"match3d-item-1","itemName":"草莓","backgroundAsset":{"prompt":"果园背景","imageSrc":"/generated-match3d-assets/session/profile/background/background.png","containerImageSrc":"/generated-match3d-assets/session/profile/ui-container/container.png","status":"image_ready"},"status":"image_ready"}]"#
.to_string(),
),
};
let row_json = to_json_string(&draft);
let restored =
parse_json::<Match3DDraftSnapshot>(&row_json, "match3d draft_json").unwrap();
assert_eq!(
restored.generated_item_assets_json.as_deref(),
draft.generated_item_assets_json.as_deref()
);
}
#[test]
fn match3d_work_update_preserves_assets_and_allows_empty_summary() {
let existing = Match3DWorkProfileRow {