Update Match3D/image-generation docs & code

Adds/updates documentation, assets and implementation for Match3D and puzzle image generation workflows. Key changes: decision logs and pitfalls updated to prefer VectorEngine Gemini for Match3D material sheets and to require edits (multipart) for 1:1 container reference images; guidance added for when to use APIMart vs VectorEngine. .env.example clarified APIMart/Responses config. Many new public assets and PPT visuals added. Code changes across frontend and backend: updated shared contracts, server-rs match3d/puzzle/image-generation handlers, VectorEngine/OpenAI image generation clients, and multiple React components/tests to handle UI/background/container image signing, edits workflow, and puzzle UI background resolution. Added src/services/puzzle-runtime/puzzleUiBackgroundSource.ts and related test updates. Includes notes about multipart HTTP/1.1 requirement and test/verification commands in docs.
This commit is contained in:
2026-05-14 20:34:45 +08:00
parent d33c937ebc
commit 548db78ca7
103 changed files with 6687 additions and 3270 deletions

View File

@@ -1070,6 +1070,9 @@ pub fn start_run_with_shuffle_seed_at(
ui_background_image_src: current_profile_level
.as_ref()
.and_then(|level| level.ui_background_image_src.clone()),
ui_background_image_object_key: current_profile_level
.as_ref()
.and_then(|level| level.ui_background_image_object_key.clone()),
background_music: current_profile_level
.as_ref()
.and_then(|level| level.background_music.clone()),
@@ -1347,6 +1350,9 @@ pub fn advance_next_level_at(
ui_background_image_src: current_profile_level
.as_ref()
.and_then(|level| level.ui_background_image_src.clone()),
ui_background_image_object_key: current_profile_level
.as_ref()
.and_then(|level| level.ui_background_image_object_key.clone()),
background_music: current_profile_level
.as_ref()
.and_then(|level| level.background_music.clone()),
@@ -1426,6 +1432,9 @@ pub fn advance_to_new_work_first_level_at(
ui_background_image_src: current_profile_level
.as_ref()
.and_then(|level| level.ui_background_image_src.clone()),
ui_background_image_object_key: current_profile_level
.as_ref()
.and_then(|level| level.ui_background_image_object_key.clone()),
background_music: current_profile_level
.as_ref()
.and_then(|level| level.background_music.clone()),
@@ -3131,14 +3140,50 @@ mod tests {
title: Some("奇境初见".to_string()),
updated_at: Some("2026-05-12T00:00:00Z".to_string()),
});
profile.levels[0].ui_background_image_object_key =
Some("generated-puzzle-assets/background-ui.png".to_string());
let run = start_run("run-music".to_string(), &profile, 0).expect("run");
let current_level = run.current_level.as_ref().expect("level");
assert_eq!(
run.current_level
.and_then(|level| level.background_music)
.map(|music| music.audio_src),
current_level
.background_music
.as_ref()
.map(|music| music.audio_src.as_str()),
Some("/generated-puzzle-assets/background.mp3".to_string())
.as_deref()
);
assert_eq!(
current_level.ui_background_image_object_key.as_deref(),
Some("generated-puzzle-assets/background-ui.png")
);
}
#[test]
fn advance_to_new_work_first_level_carries_ui_background_object_key() {
let first_profile = build_published_profile("entry", "owner-a", vec!["奇幻"]);
let mut next_profile = build_published_profile("next", "owner-b", vec!["奇幻"]);
next_profile.levels[0].ui_background_image_object_key =
Some("generated-puzzle-assets/next-ui.png".to_string());
let run = start_run("run-ui".to_string(), &first_profile, 2).expect("run");
let mut cleared_run = run.clone();
cleared_run.cleared_level_count = cleared_run.current_level_index;
let current_level = cleared_run.current_level.as_mut().expect("level");
current_level.status = PuzzleRuntimeLevelStatus::Cleared;
current_level.cleared_at_ms = Some(2_000);
current_level.elapsed_ms = Some(1_000);
let next_run =
advance_to_new_work_first_level_at(&cleared_run, &next_profile, 3_000).expect("next run");
assert_eq!(
next_run
.current_level
.as_ref()
.and_then(|level| level.ui_background_image_object_key.as_deref()),
Some("generated-puzzle-assets/next-ui.png")
);
}

View File

@@ -365,6 +365,8 @@ pub struct PuzzleRuntimeLevelSnapshot {
#[serde(default)]
pub ui_background_image_src: Option<String>,
#[serde(default)]
pub ui_background_image_object_key: Option<String>,
#[serde(default)]
pub background_music: Option<PuzzleAudioAsset>,
pub board: PuzzleBoardSnapshot,
pub status: PuzzleRuntimeLevelStatus,