This commit is contained in:
2026-05-01 01:30:02 +08:00
parent aabad6407f
commit 2e9d0f4640
92 changed files with 4548 additions and 248 deletions

View File

@@ -0,0 +1,18 @@
{
"provider": "ark",
"protocol": "responses",
"model": "deepseek-v3-2-251201",
"stream": false,
"attempt": 1,
"maxTokens": null,
"messages": [
{
"role": "system",
"content": "你是严格的世界草稿 JSON 生成器。\n只输出一个 JSON 对象,不要输出 Markdown、代码块、解释或额外文字。"
},
{
"role": "user",
"content": "请先根据下面的玩家设定创建一份“世界核心骨架”,后续我会分步骤生成角色名单、场景名单和详细档案。\n你必须只输出一个能被 JSON.parse 直接解析的 JSON 对象,不要输出 Markdown、代码块、注释或解释。\n这一步只保留世界顶层信息与一个开局归处占位不要输出 playableNpcs、storyNpcs、landmarks也不要展开人物、地图细节或多幕场景内容。\n玩家设定\n世界承诺{\"hook\":\"在失真的海图上追查一场被篡改的沉船事故。\"}\n玩家切入口{\"entryMotivation\":\"查清父亲沉船真相\",\"openingIdentity\":\"被停职返乡的守灯人\",\"openingProblem\":\"灯塔记录被人改写\"}\n\n输出 JSON 模板:\n{\n \"name\": \"世界名称\",\n \"subtitle\": \"世界副标题\",\n \"summary\": \"世界概述\",\n \"tone\": \"世界基调\",\n \"playerGoal\": \"玩家核心目标\",\n \"templateWorldType\": \"WUXIA|XIANXIA\",\n \"majorFactions\": [\"势力甲\", \"势力乙\"],\n \"coreConflicts\": [\"冲突甲\", \"冲突乙\"],\n \"attributeSchema\": {\n \"slots\": [\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" }\n ]\n },\n \"camp\": {\n \"name\": \"开局归处名称\",\n \"description\": \"这是玩家进入世界后的第一处落脚点描述\"\n }\n}\n\n要求\n- 所有生成文本都必须使用中文。\n- 这一步只输出顶层 10 个字段name、subtitle、summary、tone、playerGoal、templateWorldType、majorFactions、coreConflicts、attributeSchema、camp。\n- 这是一个完全独立的自定义世界;不要在任何正文里直接写出“武侠世界”“仙侠世界”等现成世界名。\n- templateWorldType 只是系统兼容字段,不代表正文应当引用的世界名称。\n- camp 只表示玩家开局时的落脚处占位,更接近归舍、住处、栖居、前哨居所这类“家/归处”的概念;不要在这一步生成开局场景任务、三幕事件或三幕背景。\n- 不要输出 playableNpcs、storyNpcs、landmarks、items也不要输出任何角色和地图细节。\n- majorFactions 保持 2 到 3 个coreConflicts 保持 2 到 3 个。\n- attributeSchema 必须是本世界专属的角色六维名称体系slots 必须恰好 6 个,每个 slot 只输出 name维度名必须是 2 到 4 个汉字且互不重复。\n- attributeSchema.slots 的 name 禁止使用:生命、法力、护甲、攻击、防御、力量、敏捷、智力、精神;不要写通用 DND 或传统四维属性。\n- 不要在 attributeSchema.slots 内输出 definition、positiveSignals、negativeSignals、combatUseText、socialUseText、explorationUseText 或其他说明字段。\n- 世界设定必须直接源自玩家输入,不要脱离主题乱扩写。\n- 每个字符串尽量简洁subtitle 控制在 8 到 18 个汉字内summary 控制在 16 到 32 个汉字内tone 控制在 6 到 16 个汉字内playerGoal 控制在 16 到 32 个汉字内camp.description 控制在 18 到 40 个汉字内。\n- 返回前自检:必须是一个能被 JSON.parse 直接解析的单个 JSON 对象。"
}
]
}

View File

@@ -0,0 +1 @@
{"choices":[{"message":{"content":"{\"name\":\"雾港归航\",\"subtitle\":\"失灯旧案\",\"summary\":\"守灯人与群岛议会围绕沉船旧案对峙。\",\"tone\":\"海雾悬疑\",\"playerGoal\":\"查清父亲沉船真相\",\"templateWorldType\":\"WUXIA\",\"majorFactions\":[\"群岛议会\",\"灯塔署\"],\"coreConflicts\":[\"守灯塔的旧档案被人改写。\"],\"attributeSchema\":{\"slots\":[{\"name\":\"灯骨\"},{\"name\":\"潮步\"},{\"name\":\"灯识\"},{\"name\":\"雾魄\"},{\"name\":\"旧约\"},{\"name\":\"回澜\"}]},\"camp\":{\"name\":\"旧灯塔归舍\",\"description\":\"海雾边缘的守灯人旧居。\"}}"}}],"id":"resp_01"}

View File

@@ -83,13 +83,13 @@ use crate::{
phone_auth::{phone_login, send_phone_code},
profile_identity::update_profile_identity,
puzzle::{
advance_local_puzzle_next_level, advance_puzzle_next_level, create_puzzle_agent_session,
delete_puzzle_work, execute_puzzle_agent_action, get_puzzle_agent_session,
get_puzzle_gallery_detail, get_puzzle_run, get_puzzle_work_detail, get_puzzle_works,
list_puzzle_gallery, put_puzzle_work, record_puzzle_gallery_like,
remix_puzzle_gallery_work, start_puzzle_run, stream_puzzle_agent_message,
submit_puzzle_agent_message, submit_puzzle_leaderboard, swap_puzzle_pieces,
update_puzzle_run_pause, use_puzzle_runtime_prop,
advance_local_puzzle_next_level, advance_puzzle_next_level,
claim_puzzle_work_point_incentive, create_puzzle_agent_session, delete_puzzle_work,
execute_puzzle_agent_action, get_puzzle_agent_session, get_puzzle_gallery_detail,
get_puzzle_run, get_puzzle_work_detail, get_puzzle_works, list_puzzle_gallery,
put_puzzle_work, record_puzzle_gallery_like, remix_puzzle_gallery_work, start_puzzle_run,
stream_puzzle_agent_message, submit_puzzle_agent_message, submit_puzzle_leaderboard,
swap_puzzle_pieces, update_puzzle_run_pause, use_puzzle_runtime_prop,
},
refresh_session::refresh_session,
request_context::{attach_request_context, resolve_request_id},
@@ -764,6 +764,13 @@ pub fn build_router(state: AppState) -> Router {
require_bearer_auth,
)),
)
.route(
"/api/runtime/puzzle/works/{profile_id}/point-incentive/claim",
post(claim_puzzle_work_point_incentive).route_layer(middleware::from_fn_with_state(
state.clone(),
require_bearer_auth,
)),
)
.route("/api/runtime/puzzle/gallery", get(list_puzzle_gallery))
.route(
"/api/runtime/puzzle/gallery/{profile_id}",

View File

@@ -17,7 +17,10 @@ use module_assets::{
AssetObjectAccessPolicy, AssetObjectFieldError, build_asset_entity_binding_input,
build_asset_object_upsert_input, generate_asset_binding_id, generate_asset_object_id,
};
use module_puzzle::{PuzzleBoardSnapshot, PuzzleGeneratedImageCandidate, PuzzleRuntimeLevelStatus};
use module_puzzle::{
PuzzleBoardSnapshot, PuzzleGeneratedImageCandidate, PuzzleRuntimeLevelStatus,
PuzzleWorkProfile, resolve_puzzle_level_config,
};
use platform_oss::{
LegacyAssetPrefix, OssHeadObjectRequest, OssObjectAccess, OssPutObjectRequest,
OssSignedGetObjectUrlRequest,
@@ -61,8 +64,9 @@ use spacetime_client::{
PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, PuzzleResultPreviewRecord,
PuzzleRunPauseRecordInput, PuzzleRunPropRecordInput, PuzzleRunRecord,
PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput, PuzzleRuntimeLevelRecord,
PuzzleSelectCoverImageRecordInput, PuzzleWorkLikeReportRecordInput, PuzzleWorkProfileRecord,
PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput, SpacetimeClientError,
PuzzleSelectCoverImageRecordInput, PuzzleWorkLikeReportRecordInput,
PuzzleWorkPointIncentiveClaimRecordInput, PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput,
PuzzleWorkUpsertRecordInput, SpacetimeClientError,
};
use std::convert::Infallible;
use tokio::time::sleep;
@@ -966,6 +970,43 @@ pub async fn delete_puzzle_work(
))
}
pub async fn claim_puzzle_work_point_incentive(
State(state): State<AppState>,
AxumPath(profile_id): AxumPath<String>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, Response> {
ensure_non_empty(
&request_context,
PUZZLE_WORKS_PROVIDER,
&profile_id,
"profileId",
)?;
let item = state
.spacetime_client()
.claim_puzzle_work_point_incentive(PuzzleWorkPointIncentiveClaimRecordInput {
profile_id,
owner_user_id: authenticated.claims().user_id().to_string(),
claimed_at_micros: current_utc_micros(),
})
.await
.map_err(|error| {
puzzle_error_response(
&request_context,
PUZZLE_WORKS_PROVIDER,
map_puzzle_client_error(error),
)
})?;
Ok(json_success_body(
Some(&request_context),
PuzzleWorkMutationResponse {
item: map_puzzle_work_profile_response(&state, item),
},
))
}
pub async fn list_puzzle_gallery(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
@@ -1370,6 +1411,7 @@ pub async fn use_puzzle_runtime_prop(
owner_user_id: reducer_owner_user_id,
prop_kind,
used_at_micros: current_utc_micros(),
spent_points: crate::asset_billing::ASSET_OPERATION_POINTS_COST,
})
.await
.map_err(map_puzzle_client_error)
@@ -1689,6 +1731,13 @@ fn map_puzzle_work_summary_response(
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,
levels: Vec::new(),
}
@@ -1898,7 +1947,8 @@ fn map_puzzle_board_request_record(board: PuzzleBoardSnapshotResponse) -> Puzzle
fn map_puzzle_runtime_level_response(
level: spacetime_client::PuzzleRuntimeLevelRecord,
) -> PuzzleRuntimeLevelSnapshotResponse {
let timer_defaults = build_puzzle_runtime_timer_response_defaults(level.grid_size);
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 {
@@ -1945,9 +1995,14 @@ struct PuzzleRuntimeTimerResponseDefaults {
}
fn build_puzzle_runtime_timer_response_defaults(
level_index: u32,
grid_size: u32,
) -> PuzzleRuntimeTimerResponseDefaults {
let time_limit_ms = module_puzzle::resolve_puzzle_level_time_limit_ms(grid_size);
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 }
}
@@ -2697,8 +2752,11 @@ async fn build_local_next_puzzle_run(
return Ok(next_run);
}
if let Some(gallery_item) = resolve_gallery_next_puzzle_work(state, &run).await? {
return Ok(build_next_run_from_puzzle_work(state, run, gallery_item));
let current_work = fetch_local_current_work_detail(state, &run).await?;
let similar_works =
resolve_gallery_similar_puzzle_works(state, &run, current_work.as_ref()).await?;
if !similar_works.is_empty() {
return Ok(build_local_similar_works_handoff(run, similar_works));
}
if source_session_id.trim().is_empty() {
@@ -2886,23 +2944,187 @@ async fn fetch_local_current_work_detail(
}
}
async fn resolve_gallery_next_puzzle_work(
async fn resolve_gallery_similar_puzzle_works(
state: &AppState,
run: &PuzzleRunRecord,
) -> Result<Option<PuzzleWorkProfileRecord>, AppError> {
current_work: Option<&PuzzleWorkProfileRecord>,
) -> Result<Vec<PuzzleRecommendedNextWorkRecord>, AppError> {
let Some(current_profile) = build_recommendation_current_profile(run, current_work) else {
return Ok(Vec::new());
};
let items = state
.spacetime_client()
.list_puzzle_gallery()
.await
.map_err(map_puzzle_client_error)?;
Ok(items.into_iter().find(|item| {
item.publication_status == "published"
&& item
.cover_image_src
.as_ref()
.is_some_and(|value| !value.is_empty())
&& !run.played_profile_ids.contains(&item.profile_id)
}))
let candidates = items
.iter()
.map(map_puzzle_work_profile_domain)
.collect::<Vec<_>>();
Ok(module_puzzle::select_next_profiles(
&current_profile,
&run.played_profile_ids,
&candidates,
3,
)
.into_iter()
.map(|candidate| build_recommended_next_work_record(&current_profile, candidate))
.collect())
}
fn build_local_similar_works_handoff(
mut run: PuzzleRunRecord,
recommended_next_works: Vec<PuzzleRecommendedNextWorkRecord>,
) -> PuzzleRunRecord {
let next_profile_id = recommended_next_works
.first()
.map(|item| item.profile_id.clone());
run.recommended_next_profile_id = next_profile_id.clone();
run.next_level_mode = module_puzzle::PUZZLE_NEXT_LEVEL_MODE_SIMILAR_WORKS.to_string();
run.next_level_profile_id = next_profile_id;
run.next_level_id = None;
run.recommended_next_works = recommended_next_works;
run
}
fn build_recommendation_current_profile(
run: &PuzzleRunRecord,
current_work: Option<&PuzzleWorkProfileRecord>,
) -> Option<PuzzleWorkProfile> {
if let Some(work) = current_work {
return Some(map_puzzle_work_profile_domain(work));
}
let level = run.current_level.as_ref()?;
Some(PuzzleWorkProfile {
work_id: format!("runtime-work-{}", level.profile_id),
profile_id: level.profile_id.clone(),
owner_user_id: String::new(),
source_session_id: None,
author_display_name: level.author_display_name.clone(),
work_title: level.level_name.clone(),
work_description: String::new(),
level_name: level.level_name.clone(),
summary: String::new(),
theme_tags: level.theme_tags.clone(),
cover_image_src: level.cover_image_src.clone(),
cover_asset_id: None,
levels: Vec::new(),
publication_status: module_puzzle::PuzzlePublicationStatus::Published,
updated_at_micros: 0,
published_at_micros: None,
play_count: 0,
remix_count: 0,
like_count: 0,
recent_play_count_7d: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
publish_ready: true,
anchor_pack: module_puzzle::empty_anchor_pack(),
})
}
fn map_puzzle_work_profile_domain(item: &PuzzleWorkProfileRecord) -> PuzzleWorkProfile {
PuzzleWorkProfile {
work_id: item.work_id.clone(),
profile_id: item.profile_id.clone(),
owner_user_id: item.owner_user_id.clone(),
source_session_id: item.source_session_id.clone(),
author_display_name: item.author_display_name.clone(),
work_title: item.work_title.clone(),
work_description: item.work_description.clone(),
level_name: item.level_name.clone(),
summary: item.summary.clone(),
theme_tags: item.theme_tags.clone(),
cover_image_src: item.cover_image_src.clone(),
cover_asset_id: item.cover_asset_id.clone(),
levels: item
.levels
.iter()
.map(map_puzzle_draft_level_domain)
.collect(),
publication_status: match item.publication_status.as_str() {
"published" => module_puzzle::PuzzlePublicationStatus::Published,
_ => module_puzzle::PuzzlePublicationStatus::Draft,
},
updated_at_micros: parse_puzzle_record_timestamp_micros(&item.updated_at),
published_at_micros: item
.published_at
.as_deref()
.map(parse_puzzle_record_timestamp_micros),
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,
publish_ready: item.publish_ready,
anchor_pack: module_puzzle::empty_anchor_pack(),
}
}
fn map_puzzle_draft_level_domain(
level: &PuzzleDraftLevelRecord,
) -> module_puzzle::PuzzleDraftLevel {
module_puzzle::PuzzleDraftLevel {
level_id: level.level_id.clone(),
level_name: level.level_name.clone(),
picture_description: level.picture_description.clone(),
candidates: level
.candidates
.iter()
.map(map_puzzle_generated_image_candidate_domain)
.collect(),
selected_candidate_id: level.selected_candidate_id.clone(),
cover_image_src: level.cover_image_src.clone(),
cover_asset_id: level.cover_asset_id.clone(),
generation_status: level.generation_status.clone(),
}
}
fn map_puzzle_generated_image_candidate_domain(
candidate: &PuzzleGeneratedImageCandidateRecord,
) -> PuzzleGeneratedImageCandidate {
PuzzleGeneratedImageCandidate {
candidate_id: candidate.candidate_id.clone(),
image_src: candidate.image_src.clone(),
asset_id: candidate.asset_id.clone(),
prompt: candidate.prompt.clone(),
actual_prompt: candidate.actual_prompt.clone(),
source_type: candidate.source_type.clone(),
selected: candidate.selected,
}
}
fn build_recommended_next_work_record(
current_profile: &PuzzleWorkProfile,
candidate: &PuzzleWorkProfile,
) -> PuzzleRecommendedNextWorkRecord {
PuzzleRecommendedNextWorkRecord {
profile_id: candidate.profile_id.clone(),
level_name: candidate.level_name.clone(),
author_display_name: candidate.author_display_name.clone(),
theme_tags: candidate.theme_tags.clone(),
cover_image_src: candidate.cover_image_src.clone(),
similarity_score: module_puzzle::tag_similarity_score(
&current_profile.theme_tags,
&candidate.theme_tags,
),
}
}
fn parse_puzzle_record_timestamp_micros(value: &str) -> i64 {
let Some((seconds, rest)) = value.split_once('.') else {
return 0;
};
let micros = rest.strip_suffix('Z').unwrap_or(rest);
let Ok(seconds) = seconds.parse::<i64>() else {
return 0;
};
let Ok(micros) = micros.parse::<i64>() else {
return 0;
};
seconds.saturating_mul(1_000_000).saturating_add(micros)
}
fn pick_unused_puzzle_candidate<'a>(
@@ -2987,27 +3209,6 @@ fn resolve_level_cover_image_src(level: &PuzzleDraftLevelRecord) -> Option<Strin
})
}
fn build_next_run_from_puzzle_work(
state: &AppState,
run: PuzzleRunRecord,
item: PuzzleWorkProfileRecord,
) -> PuzzleRunRecord {
let author = resolve_work_author_by_user_id(
state,
&item.owner_user_id,
Some(&item.author_display_name),
None,
);
build_next_run_from_parts(
run,
item.profile_id,
item.level_name,
author.display_name,
item.theme_tags,
item.cover_image_src,
)
}
fn build_next_run_from_candidate(
run: PuzzleRunRecord,
session: &PuzzleAgentSessionRecord,
@@ -3089,8 +3290,9 @@ fn build_next_run_from_parts_with_handoff(
next_after_level_id: Option<String>,
) -> PuzzleRunRecord {
let next_level_index = run.current_level_index + 1;
let grid_size = if run.cleared_level_count >= 3 { 4 } else { 3 };
let time_limit_ms = module_puzzle::resolve_puzzle_level_time_limit_ms(grid_size);
let level_config = resolve_puzzle_level_config(next_level_index);
let grid_size = level_config.grid_size;
let time_limit_ms = level_config.time_limit_ms;
let mut played_profile_ids = run.played_profile_ids.clone();
let current_level_id = run.next_level_id.clone();
if !played_profile_ids.contains(&profile_id) {
@@ -3250,6 +3452,98 @@ mod tests {
assert!(!has_original_neighbor_pair(&third));
}
fn test_recommended_work(profile_id: &str, score: f32) -> PuzzleRecommendedNextWorkRecord {
PuzzleRecommendedNextWorkRecord {
profile_id: profile_id.to_string(),
level_name: format!("{profile_id} 关"),
author_display_name: "作者".to_string(),
theme_tags: vec!["奇幻".to_string()],
cover_image_src: Some(format!("/{profile_id}.png")),
similarity_score: score,
}
}
#[test]
fn local_similar_works_handoff_keeps_cleared_run_for_user_choice() {
let run = PuzzleRunRecord {
run_id: "local-puzzle-run-a".to_string(),
entry_profile_id: "profile-current".to_string(),
cleared_level_count: 1,
current_level_index: 1,
current_grid_size: 3,
played_profile_ids: vec!["profile-current".to_string()],
previous_level_tags: vec!["奇幻".to_string()],
current_level: Some(PuzzleRuntimeLevelRecord {
run_id: "local-puzzle-run-a".to_string(),
level_index: 1,
level_id: Some("puzzle-level-1".to_string()),
grid_size: 3,
profile_id: "profile-current".to_string(),
level_name: "当前拼图".to_string(),
author_display_name: "当前作者".to_string(),
theme_tags: vec!["奇幻".to_string()],
cover_image_src: Some("/current.png".to_string()),
board: build_local_puzzle_board(3, "local-puzzle-run-a", "profile-current", 1),
status: "cleared".to_string(),
started_at_ms: 1_000,
cleared_at_ms: Some(2_000),
elapsed_ms: Some(1_000),
time_limit_ms: 300_000,
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: module_puzzle::PUZZLE_NEXT_LEVEL_MODE_NONE.to_string(),
next_level_profile_id: None,
next_level_id: None,
recommended_next_works: Vec::new(),
leaderboard_entries: Vec::new(),
};
let next_run = build_local_similar_works_handoff(
run,
vec![
test_recommended_work("profile-a", 0.9),
test_recommended_work("profile-b", 0.8),
test_recommended_work("profile-c", 0.7),
],
);
assert_eq!(
next_run.next_level_mode,
module_puzzle::PUZZLE_NEXT_LEVEL_MODE_SIMILAR_WORKS
);
assert_eq!(
next_run.recommended_next_profile_id.as_deref(),
Some("profile-a")
);
assert_eq!(next_run.next_level_profile_id.as_deref(), Some("profile-a"));
assert_eq!(next_run.next_level_id, None);
assert_eq!(next_run.recommended_next_works.len(), 3);
assert_eq!(next_run.current_level_index, 1);
assert_eq!(
next_run
.current_level
.as_ref()
.map(|level| level.status.as_str()),
Some("cleared")
);
}
#[test]
fn puzzle_record_timestamp_parser_matches_shared_format() {
assert_eq!(
parse_puzzle_record_timestamp_micros("1713686401.234567Z"),
1_713_686_401_234_567
);
assert_eq!(parse_puzzle_record_timestamp_micros("bad-value"), 0);
}
#[test]
fn puzzle_generated_image_size_is_square_1_1() {
assert_eq!(PUZZLE_GENERATED_IMAGE_SIZE, "1024*1024");

View File

@@ -21,6 +21,7 @@ use shared_contracts::runtime::{
PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITEE_REWARD,
PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITER_REWARD,
PROFILE_WALLET_LEDGER_SOURCE_TYPE_POINTS_RECHARGE,
PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM,
PROFILE_WALLET_LEDGER_SOURCE_TYPE_REDEEM_CODE_REWARD,
PROFILE_WALLET_LEDGER_SOURCE_TYPE_SNAPSHOT_SYNC, ProfileDashboardSummaryResponse,
ProfileMembershipBenefitResponse, ProfileMembershipResponse, ProfilePlayStatsResponse,
@@ -127,6 +128,9 @@ fn format_profile_wallet_ledger_source_type(
RuntimeProfileWalletLedgerSourceType::RedeemCodeReward => {
PROFILE_WALLET_LEDGER_SOURCE_TYPE_REDEEM_CODE_REWARD
}
RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim => {
PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM
}
}
}
@@ -562,7 +566,7 @@ mod tests {
use crate::{app::build_router, config::AppConfig, state::AppState};
#[test]
fn profile_wallet_ledger_source_type_formats_asset_operation_values() {
fn profile_wallet_ledger_source_type_formats_backend_values() {
assert_eq!(
format_profile_wallet_ledger_source_type(
RuntimeProfileWalletLedgerSourceType::AssetOperationConsume
@@ -575,6 +579,12 @@ mod tests {
),
shared_contracts::runtime::PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_REFUND
);
assert_eq!(
format_profile_wallet_ledger_source_type(
RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim
),
shared_contracts::runtime::PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM
);
}
#[tokio::test]

View File

@@ -20,8 +20,16 @@ pub const PUZZLE_EXTEND_TIME_DURATION_MS: u64 = 60_000;
pub const PUZZLE_NEXT_LEVEL_MODE_SAME_WORK: &str = "sameWork";
pub const PUZZLE_NEXT_LEVEL_MODE_SIMILAR_WORKS: &str = "similarWorks";
pub const PUZZLE_NEXT_LEVEL_MODE_NONE: &str = "none";
pub const PUZZLE_SUPPORTED_GRID_SIZES: [u32; 5] = [3, 4, 5, 6, 7];
const PUZZLE_INITIAL_SHUFFLE_ATTEMPTS: u64 = 64;
// 中文注释:拼图难度只从关卡序号解析,避免切割规格和倒计时在不同入口各写一套。
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PuzzleLevelConfig {
pub grid_size: u32,
pub time_limit_ms: u64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PuzzleAgentStage {
@@ -257,6 +265,10 @@ pub struct PuzzleWorkProfile {
pub like_count: u32,
#[serde(default)]
pub recent_play_count_7d: u32,
#[serde(default)]
pub point_incentive_total_half_points: u64,
#[serde(default)]
pub point_incentive_claimed_points: u64,
pub publish_ready: bool,
pub anchor_pack: PuzzleAnchorPack,
}
@@ -540,6 +552,14 @@ pub struct PuzzleWorkLikeRecordInput {
pub liked_at_micros: i64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PuzzleWorkPointIncentiveClaimInput {
pub profile_id: String,
pub owner_user_id: String,
pub claimed_at_micros: i64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PuzzleRunStartInput {
@@ -602,6 +622,8 @@ pub struct PuzzleRunPropInput {
pub owner_user_id: String,
pub prop_kind: String,
pub used_at_micros: i64,
#[serde(default)]
pub spent_points: u64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
@@ -1298,6 +1320,8 @@ pub fn create_work_profile(
remix_count: 0,
like_count: 0,
recent_play_count_7d: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
publish_ready: preview.publish_ready,
anchor_pack: draft.anchor_pack.clone(),
})
@@ -1411,20 +1435,81 @@ pub fn normalize_puzzle_levels(
Ok(normalized_levels)
}
pub fn is_supported_puzzle_grid_size(grid_size: u32) -> bool {
PUZZLE_SUPPORTED_GRID_SIZES.contains(&grid_size)
}
pub fn resolve_puzzle_level_config(level_index: u32) -> PuzzleLevelConfig {
let level_index = level_index.max(1);
match level_index {
1 => PuzzleLevelConfig {
grid_size: 3,
time_limit_ms: 300_000,
},
2 => PuzzleLevelConfig {
grid_size: 4,
time_limit_ms: 300_000,
},
3 => PuzzleLevelConfig {
grid_size: 5,
time_limit_ms: 300_000,
},
4 => PuzzleLevelConfig {
grid_size: 5,
time_limit_ms: 210_000,
},
_ => {
let loop_index = (level_index.saturating_sub(5) % 6) + 5;
match loop_index {
5 => PuzzleLevelConfig {
grid_size: 5,
time_limit_ms: 210_000,
},
6 => PuzzleLevelConfig {
grid_size: 6,
time_limit_ms: 240_000,
},
7 => PuzzleLevelConfig {
grid_size: 5,
time_limit_ms: 210_000,
},
8 => PuzzleLevelConfig {
grid_size: 7,
time_limit_ms: 270_000,
},
9 => PuzzleLevelConfig {
grid_size: 5,
time_limit_ms: 240_000,
},
_ => PuzzleLevelConfig {
grid_size: 7,
time_limit_ms: 270_000,
},
}
}
}
}
pub fn resolve_puzzle_grid_size(cleared_level_count: u32) -> u32 {
if cleared_level_count >= 3 { 4 } else { 3 }
resolve_puzzle_level_config(cleared_level_count + 1).grid_size
}
pub fn resolve_puzzle_level_time_limit_ms_by_index(level_index: u32) -> u64 {
resolve_puzzle_level_config(level_index.max(1)).time_limit_ms
}
pub fn resolve_puzzle_level_time_limit_ms(grid_size: u32) -> u64 {
match grid_size {
4 => 300_000,
_ => 180_000,
3 | 4 | 5 => 300_000,
6 => 240_000,
7 => 270_000,
_ => 300_000,
}
}
pub fn resolve_puzzle_runtime_remaining_ms(level: &PuzzleRuntimeLevelSnapshot, now_ms: u64) -> u64 {
let time_limit_ms = if level.time_limit_ms == 0 {
resolve_puzzle_level_time_limit_ms(level.grid_size)
resolve_puzzle_level_time_limit_ms_by_index(level.level_index)
} else {
level.time_limit_ms
};
@@ -1436,7 +1521,7 @@ fn normalize_timer_fields(level: &mut PuzzleRuntimeLevelSnapshot, now_ms: u64) {
level.started_at_ms = now_ms;
}
if level.time_limit_ms == 0 {
level.time_limit_ms = resolve_puzzle_level_time_limit_ms(level.grid_size);
level.time_limit_ms = resolve_puzzle_level_time_limit_ms_by_index(level.level_index);
}
if level.remaining_ms == 0 && level.status == PuzzleRuntimeLevelStatus::Playing {
level.remaining_ms = level.time_limit_ms;
@@ -1612,7 +1697,7 @@ pub fn build_initial_board_with_seed(
grid_size: u32,
shuffle_seed: u64,
) -> Result<PuzzleBoardSnapshot, PuzzleFieldError> {
if !matches!(grid_size, 3 | 4) {
if !is_supported_puzzle_grid_size(grid_size) {
return Err(PuzzleFieldError::InvalidGridSize);
}
@@ -1678,19 +1763,21 @@ pub fn start_run_with_shuffle_seed_at(
shuffle_seed: u64,
started_at_ms: u64,
) -> Result<PuzzleRunSnapshot, PuzzleFieldError> {
let grid_size = resolve_puzzle_grid_size(cleared_level_count);
let level_index = cleared_level_count + 1;
let level_config = resolve_puzzle_level_config(level_index);
let grid_size = level_config.grid_size;
let board = build_initial_board_with_seed(grid_size, shuffle_seed)?;
Ok(PuzzleRunSnapshot {
run_id: run_id.clone(),
entry_profile_id: entry_profile.profile_id.clone(),
cleared_level_count,
current_level_index: cleared_level_count + 1,
current_level_index: level_index,
current_grid_size: grid_size,
played_profile_ids: vec![entry_profile.profile_id.clone()],
previous_level_tags: entry_profile.theme_tags.clone(),
current_level: Some(PuzzleRuntimeLevelSnapshot {
run_id,
level_index: cleared_level_count + 1,
level_index,
level_id: entry_profile
.levels
.first()
@@ -1706,8 +1793,8 @@ pub fn start_run_with_shuffle_seed_at(
started_at_ms,
cleared_at_ms: None,
elapsed_ms: None,
time_limit_ms: resolve_puzzle_level_time_limit_ms(grid_size),
remaining_ms: resolve_puzzle_level_time_limit_ms(grid_size),
time_limit_ms: level_config.time_limit_ms,
remaining_ms: level_config.time_limit_ms,
paused_accumulated_ms: 0,
pause_started_at_ms: None,
freeze_accumulated_ms: 0,
@@ -1938,11 +2025,13 @@ pub fn advance_next_level_at(
}
let next_cleared_count = run.cleared_level_count;
let next_grid_size = resolve_puzzle_grid_size(next_cleared_count);
let next_level_index = run.current_level_index + 1;
let next_level_config = resolve_puzzle_level_config(next_level_index);
let next_grid_size = next_level_config.grid_size;
let shuffle_seed = puzzle_shuffle_seed(
&run.run_id,
&next_profile.profile_id,
run.current_level_index + 1,
next_level_index,
next_grid_size,
);
let next_board = build_initial_board_with_seed(next_grid_size, shuffle_seed)?;
@@ -1953,13 +2042,13 @@ pub fn advance_next_level_at(
run_id: run.run_id.clone(),
entry_profile_id: run.entry_profile_id.clone(),
cleared_level_count: next_cleared_count,
current_level_index: run.current_level_index + 1,
current_level_index: next_level_index,
current_grid_size: next_grid_size,
played_profile_ids,
previous_level_tags: next_profile.theme_tags.clone(),
current_level: Some(PuzzleRuntimeLevelSnapshot {
run_id: run.run_id.clone(),
level_index: run.current_level_index + 1,
level_index: next_level_index,
level_id: next_profile
.levels
.first()
@@ -1975,8 +2064,81 @@ pub fn advance_next_level_at(
started_at_ms,
cleared_at_ms: None,
elapsed_ms: None,
time_limit_ms: resolve_puzzle_level_time_limit_ms(next_grid_size),
remaining_ms: resolve_puzzle_level_time_limit_ms(next_grid_size),
time_limit_ms: next_level_config.time_limit_ms,
remaining_ms: next_level_config.time_limit_ms,
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: default_puzzle_next_level_mode(),
next_level_profile_id: None,
next_level_id: None,
recommended_next_works: Vec::new(),
leaderboard_entries: Vec::new(),
})
}
pub fn advance_to_new_work_first_level_at(
run: &PuzzleRunSnapshot,
next_profile: &PuzzleWorkProfile,
started_at_ms: u64,
) -> Result<PuzzleRunSnapshot, PuzzleFieldError> {
let current_level = run
.current_level
.clone()
.ok_or(PuzzleFieldError::InvalidOperation)?;
if current_level.status != PuzzleRuntimeLevelStatus::Cleared {
return Err(PuzzleFieldError::InvalidOperation);
}
// 中文注释:跨作品代表进入一个新作品,关卡序号、切割规格和倒计时都从第 1 关重新开始。
let next_level_index = 1;
let level_config = resolve_puzzle_level_config(next_level_index);
let grid_size = level_config.grid_size;
let shuffle_seed = puzzle_shuffle_seed(
&run.run_id,
&next_profile.profile_id,
next_level_index,
grid_size,
);
let next_board = build_initial_board_with_seed(grid_size, shuffle_seed)?;
let mut played_profile_ids = run.played_profile_ids.clone();
if !played_profile_ids.contains(&next_profile.profile_id) {
played_profile_ids.push(next_profile.profile_id.clone());
}
Ok(PuzzleRunSnapshot {
run_id: run.run_id.clone(),
entry_profile_id: next_profile.profile_id.clone(),
cleared_level_count: 0,
current_level_index: next_level_index,
current_grid_size: grid_size,
played_profile_ids,
previous_level_tags: next_profile.theme_tags.clone(),
current_level: Some(PuzzleRuntimeLevelSnapshot {
run_id: run.run_id.clone(),
level_index: next_level_index,
level_id: next_profile
.levels
.first()
.map(|level| level.level_id.clone()),
grid_size,
profile_id: next_profile.profile_id.clone(),
level_name: next_profile.level_name.clone(),
author_display_name: next_profile.author_display_name.clone(),
theme_tags: next_profile.theme_tags.clone(),
cover_image_src: next_profile.cover_image_src.clone(),
board: next_board,
status: PuzzleRuntimeLevelStatus::Playing,
started_at_ms,
cleared_at_ms: None,
elapsed_ms: None,
time_limit_ms: level_config.time_limit_ms,
remaining_ms: level_config.time_limit_ms,
paused_accumulated_ms: 0,
pause_started_at_ms: None,
freeze_accumulated_ms: 0,
@@ -2058,6 +2220,11 @@ pub fn selected_profile_level_index(profile: &PuzzleWorkProfile, level_id: &str)
.position(|level| level.level_id == target_level_id)
}
pub fn resolve_restart_cleared_level_count(profile: &PuzzleWorkProfile, level_id: &str) -> u32 {
// 中文注释:失败重开指定的是当前关 levelIdstart_run_at 用“已通关数 + 1”计算当前关所以这里返回关卡下标。
selected_profile_level_index(profile, level_id).unwrap_or(0) as u32
}
pub fn select_next_profile<'a>(
current_profile: &PuzzleWorkProfile,
played_profile_ids: &[String],
@@ -2618,7 +2785,8 @@ fn build_initial_pieces_without_correct_neighbors(
}
// 随机尝试耗尽后使用确定性约束搜索兜底,保证开局没有任意一对原图相邻块互相贴边。
let fallback_pieces = build_original_neighbor_free_pieces(grid_size, shuffle_seed)
let fallback_pieces = build_deterministic_neighbor_free_pieces(grid_size, shuffle_seed)
.or_else(|| build_original_neighbor_free_pieces(grid_size, shuffle_seed))
.unwrap_or_else(|| build_pieces_from_positions(grid_size, &base_positions));
debug_assert!(!has_any_original_neighbor_pair(&fallback_pieces));
fallback_pieces
@@ -2686,6 +2854,124 @@ fn are_original_neighbors(left: &PuzzlePieceState, right: &PuzzlePieceState) ->
left.correct_row.abs_diff(right.correct_row) + left.correct_col.abs_diff(right.correct_col) == 1
}
fn build_deterministic_neighbor_free_pieces(
grid_size: u32,
shuffle_seed: u64,
) -> Option<Vec<PuzzlePieceState>> {
// 中文注释:大棋盘随机命中“无原图相邻贴边”的概率较低,失败后用确定性排列兜底保证稳定开局。
let positions = match grid_size {
3 => build_seeded_3x3_neighbor_free_positions(shuffle_seed),
4 | 6 => build_affine_neighbor_free_positions(grid_size, 1, 1, 2, 1, shuffle_seed),
5 | 7 => {
build_affine_neighbor_free_positions(grid_size, 0, 1, 2, grid_size - 1, shuffle_seed)
}
_ => return None,
};
let pieces = build_pieces_from_positions(grid_size, &positions);
(!has_any_original_neighbor_pair(&pieces)).then_some(pieces)
}
fn build_seeded_3x3_neighbor_free_positions(shuffle_seed: u64) -> Vec<PuzzleCellPosition> {
const LAYOUTS: [[(u32, u32); 9]; 6] = [
[
(0, 1),
(1, 0),
(1, 2),
(2, 0),
(0, 2),
(2, 1),
(1, 1),
(2, 2),
(0, 0),
],
[
(0, 1),
(1, 0),
(1, 2),
(2, 0),
(0, 2),
(2, 1),
(2, 2),
(1, 1),
(0, 0),
],
[
(0, 1),
(1, 0),
(1, 2),
(2, 0),
(2, 2),
(0, 0),
(1, 1),
(0, 2),
(2, 1),
],
[
(0, 1),
(1, 0),
(1, 2),
(2, 1),
(0, 2),
(2, 0),
(0, 0),
(2, 2),
(1, 1),
],
[
(0, 1),
(1, 0),
(1, 2),
(2, 2),
(0, 2),
(2, 1),
(1, 1),
(2, 0),
(0, 0),
],
[
(0, 1),
(1, 0),
(2, 1),
(2, 0),
(2, 2),
(0, 2),
(1, 2),
(0, 0),
(1, 1),
],
];
let layout = &LAYOUTS[(shuffle_seed as usize) % LAYOUTS.len()];
layout
.into_iter()
.map(|(row, col)| PuzzleCellPosition {
row: *row,
col: *col,
})
.collect()
}
fn build_affine_neighbor_free_positions(
grid_size: u32,
row_from_row: u32,
row_from_col: u32,
col_from_row: u32,
col_from_col: u32,
shuffle_seed: u64,
) -> Vec<PuzzleCellPosition> {
let row_offset = (shuffle_seed % u64::from(grid_size)) as u32;
let col_offset = ((shuffle_seed / u64::from(grid_size)) % u64::from(grid_size)) as u32;
(0..(grid_size * grid_size))
.map(|index| {
let row = index / grid_size;
let col = index % grid_size;
PuzzleCellPosition {
row: (row_from_row * row + row_from_col * col + row_offset) % grid_size,
col: (col_from_row * row + col_from_col * col + col_offset) % grid_size,
}
})
.collect()
}
fn build_original_neighbor_free_pieces(
grid_size: u32,
shuffle_seed: u64,
@@ -3212,6 +3498,8 @@ mod tests {
recent_play_count_7d: 0,
remix_count: 0,
like_count: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
publish_ready: true,
anchor_pack: empty_anchor_pack(),
}
@@ -3220,8 +3508,33 @@ mod tests {
#[test]
fn resolve_grid_size_matches_prd() {
assert_eq!(resolve_puzzle_grid_size(0), 3);
assert_eq!(resolve_puzzle_grid_size(2), 3);
assert_eq!(resolve_puzzle_grid_size(3), 4);
assert_eq!(resolve_puzzle_grid_size(1), 4);
assert_eq!(resolve_puzzle_grid_size(2), 5);
assert_eq!(resolve_puzzle_grid_size(3), 5);
assert_eq!(resolve_puzzle_grid_size(4), 5);
assert_eq!(resolve_puzzle_grid_size(5), 6);
assert_eq!(resolve_puzzle_grid_size(6), 5);
assert_eq!(resolve_puzzle_grid_size(7), 7);
assert_eq!(resolve_puzzle_grid_size(8), 5);
assert_eq!(resolve_puzzle_grid_size(9), 7);
assert_eq!(resolve_puzzle_grid_size(10), 5);
assert_eq!(resolve_puzzle_grid_size(15), 7);
}
#[test]
fn resolve_level_time_limit_matches_prd() {
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(1), 300_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(2), 300_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(3), 300_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(4), 210_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(5), 210_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(6), 240_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(7), 210_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(8), 270_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(9), 240_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(10), 270_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(11), 210_000);
assert_eq!(resolve_puzzle_level_time_limit_ms_by_index(16), 270_000);
}
#[test]
@@ -3360,6 +3673,66 @@ mod tests {
assert_eq!(selected.profile_id, "b");
}
#[test]
fn restart_cleared_count_uses_selected_level_index() {
let mut profile = build_published_profile("entry", "owner-a", vec!["机关"]);
profile.levels = vec![
PuzzleDraftLevel {
level_id: "puzzle-level-1".to_string(),
level_name: "第一关".to_string(),
picture_description: "第一关画面".to_string(),
candidates: Vec::new(),
selected_candidate_id: None,
cover_image_src: Some("/level-1.png".to_string()),
cover_asset_id: None,
generation_status: "ready".to_string(),
},
PuzzleDraftLevel {
level_id: "puzzle-level-2".to_string(),
level_name: "第二关".to_string(),
picture_description: "第二关画面".to_string(),
candidates: Vec::new(),
selected_candidate_id: None,
cover_image_src: Some("/level-2.png".to_string()),
cover_asset_id: None,
generation_status: "ready".to_string(),
},
];
assert_eq!(
resolve_restart_cleared_level_count(&profile, "puzzle-level-2"),
1
);
assert_eq!(
resolve_restart_cleared_level_count(&profile, "missing-level"),
0
);
}
#[test]
fn advance_to_new_work_first_level_restarts_level_progress() {
let first_profile = build_published_profile("entry", "owner-a", vec!["奇幻", "遗迹"]);
let next_profile = build_published_profile("next", "owner-b", vec!["奇幻", "魔法"]);
let mut run = start_run("run-cross-work".to_string(), &first_profile, 2).expect("run");
run.cleared_level_count = run.current_level_index;
let current_level = 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(&run, &next_profile, 3_000).expect("next run");
assert_eq!(next_run.entry_profile_id, "next");
assert_eq!(next_run.cleared_level_count, 0);
assert_eq!(next_run.current_level_index, 1);
let next_level = next_run.current_level.expect("next level");
assert_eq!(next_level.profile_id, "next");
assert_eq!(next_level.level_index, 1);
assert_eq!(next_level.grid_size, 3);
assert_eq!(next_level.time_limit_ms, 300_000);
}
#[test]
fn swap_pieces_marks_cleared_when_back_to_origin() {
let profile = build_published_profile("entry", "owner-a", vec!["蒸汽城市", "雨夜", "猫咪"]);
@@ -3408,7 +3781,7 @@ mod tests {
#[test]
fn initial_board_has_no_original_neighbor_pairs() {
for grid_size in [3, 4] {
for grid_size in PUZZLE_SUPPORTED_GRID_SIZES {
for shuffle_seed in 0..128 {
let board = build_initial_board_with_seed(grid_size, shuffle_seed).expect("board");
@@ -3672,6 +4045,28 @@ mod tests {
assert_eq!(timed_level.elapsed_ms, Some(timed_level.time_limit_ms));
}
#[test]
fn failed_level_can_extend_one_minute() {
let profile = build_published_profile("entry", "owner-a", vec!["蒸汽城市", "雨夜", "猫咪"]);
let now_ms = current_unix_ms();
let mut run =
start_run_with_shuffle_seed("run-extend".to_string(), &profile, 0, 14).expect("run");
let level = run.current_level.as_mut().expect("level");
level.started_at_ms = now_ms.saturating_sub(level.time_limit_ms + 1_000);
let failed_run = resolve_puzzle_run_timer_at(run, now_ms);
let extended_run = extend_failed_puzzle_time_at(&failed_run, now_ms + 5_000)
.expect("extend should succeed");
let extended_level = extended_run.current_level.as_ref().expect("level");
assert_eq!(extended_level.status, PuzzleRuntimeLevelStatus::Playing);
assert_eq!(extended_level.remaining_ms, PUZZLE_EXTEND_TIME_DURATION_MS);
assert_eq!(extended_level.elapsed_ms, None);
assert_eq!(extended_level.cleared_at_ms, None);
assert_eq!(extended_level.pause_started_at_ms, None);
assert_eq!(extended_level.freeze_until_ms, None);
}
#[test]
fn pause_and_freeze_are_excluded_from_effective_timer() {
let profile = build_published_profile("entry", "owner-a", vec!["蒸汽城市", "雨夜", "猫咪"]);

View File

@@ -262,6 +262,7 @@ pub enum RuntimeProfileWalletLedgerSourceType {
AssetOperationConsume,
AssetOperationRefund,
RedeemCodeReward,
PuzzleAuthorIncentiveClaim,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
@@ -1709,6 +1710,7 @@ impl RuntimeProfileWalletLedgerSourceType {
Self::AssetOperationConsume => "asset_operation_consume",
Self::AssetOperationRefund => "asset_operation_refund",
Self::RedeemCodeReward => "redeem_code_reward",
Self::PuzzleAuthorIncentiveClaim => "puzzle_author_incentive_claim",
}
}
}
@@ -2233,6 +2235,10 @@ mod tests {
RuntimeProfileWalletLedgerSourceType::AssetOperationRefund.as_str(),
"asset_operation_refund"
);
assert_eq!(
RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim.as_str(),
"puzzle_author_incentive_claim"
);
}
#[test]

View File

@@ -47,11 +47,61 @@ pub struct PuzzleWorkSummaryResponse {
pub like_count: u32,
#[serde(default)]
pub recent_play_count_7d: u32,
#[serde(default)]
pub point_incentive_total_half_points: u64,
#[serde(default)]
pub point_incentive_claimed_points: u64,
#[serde(default)]
pub point_incentive_total_points: f64,
#[serde(default)]
pub point_incentive_claimable_points: u64,
pub publish_ready: bool,
#[serde(default)]
pub levels: Vec<PuzzleDraftLevelResponse>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn puzzle_work_summary_response_uses_point_incentive_fields() {
let payload = serde_json::to_value(PuzzleWorkSummaryResponse {
work_id: "work-1".to_string(),
profile_id: "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,
publication_status: "published".to_string(),
updated_at: "2026-05-01T00:00:00Z".to_string(),
published_at: Some("2026-05-01T00:00:00Z".to_string()),
play_count: 1,
remix_count: 0,
like_count: 0,
recent_play_count_7d: 1,
point_incentive_total_half_points: 3,
point_incentive_claimed_points: 1,
point_incentive_total_points: 1.5,
point_incentive_claimable_points: 0,
publish_ready: true,
levels: Vec::new(),
})
.expect("payload should serialize");
assert_eq!(payload["pointIncentiveTotalHalfPoints"], 3);
assert_eq!(payload["pointIncentiveClaimedPoints"], 1);
assert_eq!(payload["pointIncentiveTotalPoints"], 1.5);
assert_eq!(payload["pointIncentiveClaimablePoints"], 0);
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleWorkProfileResponse {

View File

@@ -11,6 +11,8 @@ pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_CONSUME: &str =
"asset_operation_consume";
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_REFUND: &str = "asset_operation_refund";
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_REDEEM_CODE_REWARD: &str = "redeem_code_reward";
pub const PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM: &str =
"puzzle_author_incentive_claim";
pub const BROWSE_HISTORY_THEME_MODE_MARTIAL: &str = "martial";
pub const BROWSE_HISTORY_THEME_MODE_ARCANE: &str = "arcane";
pub const BROWSE_HISTORY_THEME_MODE_MACHINA: &str = "machina";
@@ -910,6 +912,14 @@ mod tests {
.to_string(),
created_at: "2026-04-22T10:05:00Z".to_string(),
},
ProfileWalletLedgerEntryResponse {
id: "ledger-7".to_string(),
amount_delta: 2,
balance_after: 202,
source_type: PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM
.to_string(),
created_at: "2026-04-22T10:06:00Z".to_string(),
},
],
})
.expect("payload should serialize");
@@ -940,6 +950,10 @@ mod tests {
payload["entries"][5]["sourceType"],
json!(PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_REFUND)
);
assert_eq!(
payload["entries"][6]["sourceType"],
json!(PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM)
);
assert_eq!(
payload["entries"][0]["createdAt"],
json!("2026-04-22T10:00:00Z")

View File

@@ -39,9 +39,9 @@ pub use mapper::{
PuzzleResultPreviewRecord, PuzzleRunDragRecordInput, PuzzleRunNextLevelRecordInput,
PuzzleRunPauseRecordInput, PuzzleRunPropRecordInput, PuzzleRunRecord,
PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput, PuzzleRuntimeLevelRecord,
PuzzleSelectCoverImageRecordInput, PuzzleWorkLikeReportRecordInput, PuzzleWorkProfileRecord,
PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput, ResolveCombatActionRecord,
ResolveNpcBattleInteractionInput,
PuzzleSelectCoverImageRecordInput, PuzzleWorkLikeReportRecordInput,
PuzzleWorkPointIncentiveClaimRecordInput, PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput,
PuzzleWorkUpsertRecordInput, ResolveCombatActionRecord, ResolveNpcBattleInteractionInput,
};
pub mod ai;

View File

@@ -2436,6 +2436,8 @@ pub(crate) fn map_puzzle_work_profile(
remix_count: snapshot.remix_count,
like_count: snapshot.like_count,
recent_play_count_7d: snapshot.recent_play_count_7d,
point_incentive_total_half_points: snapshot.point_incentive_total_half_points,
point_incentive_claimed_points: snapshot.point_incentive_claimed_points,
publish_ready: snapshot.publish_ready,
anchor_pack: map_puzzle_anchor_pack(snapshot.anchor_pack),
levels: snapshot
@@ -2491,6 +2493,13 @@ fn map_puzzle_recommended_next_work(
pub(crate) fn map_puzzle_runtime_level_snapshot(
snapshot: DomainPuzzleRuntimeLevelSnapshot,
) -> PuzzleRuntimeLevelRecord {
// 中文注释:历史 run_json 可能缺 started_at_ms领域 serde 会回填为 0API 层继续补成 1避免前端计时器拿到无效开局时间。
let started_at_ms = if snapshot.started_at_ms == 0 {
1
} else {
snapshot.started_at_ms
};
PuzzleRuntimeLevelRecord {
run_id: snapshot.run_id,
level_index: snapshot.level_index,
@@ -2503,7 +2512,7 @@ pub(crate) fn map_puzzle_runtime_level_snapshot(
cover_image_src: snapshot.cover_image_src,
board: map_puzzle_board_snapshot(snapshot.board),
status: snapshot.status.as_str().to_string(),
started_at_ms: snapshot.started_at_ms,
started_at_ms,
cleared_at_ms: snapshot.cleared_at_ms,
elapsed_ms: snapshot.elapsed_ms,
time_limit_ms: snapshot.time_limit_ms,
@@ -3485,6 +3494,9 @@ pub(crate) fn map_runtime_profile_wallet_ledger_source_type_back(
crate::module_bindings::RuntimeProfileWalletLedgerSourceType::RedeemCodeReward => {
module_runtime::RuntimeProfileWalletLedgerSourceType::RedeemCodeReward
}
crate::module_bindings::RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim => {
module_runtime::RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim
}
}
}
@@ -4535,6 +4547,7 @@ pub struct PuzzleRunPropRecordInput {
pub owner_user_id: String,
pub prop_kind: String,
pub used_at_micros: i64,
pub spent_points: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -4716,11 +4729,20 @@ pub struct PuzzleWorkProfileRecord {
pub remix_count: u32,
pub like_count: u32,
pub recent_play_count_7d: u32,
pub point_incentive_total_half_points: u64,
pub point_incentive_claimed_points: u64,
pub publish_ready: bool,
pub anchor_pack: PuzzleAnchorPackRecord,
pub levels: Vec<PuzzleDraftLevelRecord>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PuzzleWorkPointIncentiveClaimRecordInput {
pub profile_id: String,
pub owner_user_id: String,
pub claimed_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PuzzleCellPositionRecord {
pub row: u32,

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::puzzle_work_point_incentive_claim_input_type::PuzzleWorkPointIncentiveClaimInput;
use super::puzzle_work_procedure_result_type::PuzzleWorkProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct ClaimPuzzleWorkPointIncentiveArgs {
pub input: PuzzleWorkPointIncentiveClaimInput,
}
impl __sdk::InModule for ClaimPuzzleWorkPointIncentiveArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `claim_puzzle_work_point_incentive`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait claim_puzzle_work_point_incentive {
fn claim_puzzle_work_point_incentive(&self, input: PuzzleWorkPointIncentiveClaimInput,
) {
self.claim_puzzle_work_point_incentive_then(input, |_, _| {});
}
fn claim_puzzle_work_point_incentive_then(
&self,
input: PuzzleWorkPointIncentiveClaimInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<PuzzleWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl claim_puzzle_work_point_incentive for super::RemoteProcedures {
fn claim_puzzle_work_point_incentive_then(
&self,
input: PuzzleWorkPointIncentiveClaimInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<PuzzleWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, PuzzleWorkProcedureResult>(
"claim_puzzle_work_point_incentive",
ClaimPuzzleWorkPointIncentiveArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_run_click_input_type::Match3DRunClickInput;
use super::match_3_d_click_item_procedure_result_type::Match3DClickItemProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct ClickMatch3DItemArgs {
pub input: Match3DRunClickInput,
}
impl __sdk::InModule for ClickMatch3DItemArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `click_match_3_d_item`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait click_match_3_d_item {
fn click_match_3_d_item(&self, input: Match3DRunClickInput,
) {
self.click_match_3_d_item_then(input, |_, _| {});
}
fn click_match_3_d_item_then(
&self,
input: Match3DRunClickInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DClickItemProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl click_match_3_d_item for super::RemoteProcedures {
fn click_match_3_d_item_then(
&self,
input: Match3DRunClickInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DClickItemProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DClickItemProcedureResult>(
"click_match_3_d_item",
ClickMatch3DItemArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_draft_compile_input_type::Match3DDraftCompileInput;
use super::match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct CompileMatch3DDraftArgs {
pub input: Match3DDraftCompileInput,
}
impl __sdk::InModule for CompileMatch3DDraftArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `compile_match_3_d_draft`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait compile_match_3_d_draft {
fn compile_match_3_d_draft(&self, input: Match3DDraftCompileInput,
) {
self.compile_match_3_d_draft_then(input, |_, _| {});
}
fn compile_match_3_d_draft_then(
&self,
input: Match3DDraftCompileInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl compile_match_3_d_draft for super::RemoteProcedures {
fn compile_match_3_d_draft_then(
&self,
input: Match3DDraftCompileInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>(
"compile_match_3_d_draft",
CompileMatch3DDraftArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult;
use super::match_3_d_agent_session_create_input_type::Match3DAgentSessionCreateInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct CreateMatch3DAgentSessionArgs {
pub input: Match3DAgentSessionCreateInput,
}
impl __sdk::InModule for CreateMatch3DAgentSessionArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `create_match_3_d_agent_session`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait create_match_3_d_agent_session {
fn create_match_3_d_agent_session(&self, input: Match3DAgentSessionCreateInput,
) {
self.create_match_3_d_agent_session_then(input, |_, _| {});
}
fn create_match_3_d_agent_session_then(
&self,
input: Match3DAgentSessionCreateInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl create_match_3_d_agent_session for super::RemoteProcedures {
fn create_match_3_d_agent_session_then(
&self,
input: Match3DAgentSessionCreateInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>(
"create_match_3_d_agent_session",
CreateMatch3DAgentSessionArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_work_delete_input_type::Match3DWorkDeleteInput;
use super::match_3_d_works_procedure_result_type::Match3DWorksProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct DeleteMatch3DWorkArgs {
pub input: Match3DWorkDeleteInput,
}
impl __sdk::InModule for DeleteMatch3DWorkArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `delete_match_3_d_work`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait delete_match_3_d_work {
fn delete_match_3_d_work(&self, input: Match3DWorkDeleteInput,
) {
self.delete_match_3_d_work_then(input, |_, _| {});
}
fn delete_match_3_d_work_then(
&self,
input: Match3DWorkDeleteInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorksProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl delete_match_3_d_work for super::RemoteProcedures {
fn delete_match_3_d_work_then(
&self,
input: Match3DWorkDeleteInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorksProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DWorksProcedureResult>(
"delete_match_3_d_work",
DeleteMatch3DWorkArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult;
use super::match_3_d_agent_message_finalize_input_type::Match3DAgentMessageFinalizeInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct FinalizeMatch3DAgentMessageTurnArgs {
pub input: Match3DAgentMessageFinalizeInput,
}
impl __sdk::InModule for FinalizeMatch3DAgentMessageTurnArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `finalize_match_3_d_agent_message_turn`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait finalize_match_3_d_agent_message_turn {
fn finalize_match_3_d_agent_message_turn(&self, input: Match3DAgentMessageFinalizeInput,
) {
self.finalize_match_3_d_agent_message_turn_then(input, |_, _| {});
}
fn finalize_match_3_d_agent_message_turn_then(
&self,
input: Match3DAgentMessageFinalizeInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl finalize_match_3_d_agent_message_turn for super::RemoteProcedures {
fn finalize_match_3_d_agent_message_turn_then(
&self,
input: Match3DAgentMessageFinalizeInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>(
"finalize_match_3_d_agent_message_turn",
FinalizeMatch3DAgentMessageTurnArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_run_time_up_input_type::Match3DRunTimeUpInput;
use super::match_3_d_run_procedure_result_type::Match3DRunProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct FinishMatch3DTimeUpArgs {
pub input: Match3DRunTimeUpInput,
}
impl __sdk::InModule for FinishMatch3DTimeUpArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `finish_match_3_d_time_up`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait finish_match_3_d_time_up {
fn finish_match_3_d_time_up(&self, input: Match3DRunTimeUpInput,
) {
self.finish_match_3_d_time_up_then(input, |_, _| {});
}
fn finish_match_3_d_time_up_then(
&self,
input: Match3DRunTimeUpInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl finish_match_3_d_time_up for super::RemoteProcedures {
fn finish_match_3_d_time_up_then(
&self,
input: Match3DRunTimeUpInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DRunProcedureResult>(
"finish_match_3_d_time_up",
FinishMatch3DTimeUpArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult;
use super::match_3_d_agent_session_get_input_type::Match3DAgentSessionGetInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct GetMatch3DAgentSessionArgs {
pub input: Match3DAgentSessionGetInput,
}
impl __sdk::InModule for GetMatch3DAgentSessionArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `get_match_3_d_agent_session`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait get_match_3_d_agent_session {
fn get_match_3_d_agent_session(&self, input: Match3DAgentSessionGetInput,
) {
self.get_match_3_d_agent_session_then(input, |_, _| {});
}
fn get_match_3_d_agent_session_then(
&self,
input: Match3DAgentSessionGetInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl get_match_3_d_agent_session for super::RemoteProcedures {
fn get_match_3_d_agent_session_then(
&self,
input: Match3DAgentSessionGetInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>(
"get_match_3_d_agent_session",
GetMatch3DAgentSessionArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_run_procedure_result_type::Match3DRunProcedureResult;
use super::match_3_d_run_get_input_type::Match3DRunGetInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct GetMatch3DRunArgs {
pub input: Match3DRunGetInput,
}
impl __sdk::InModule for GetMatch3DRunArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `get_match_3_d_run`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait get_match_3_d_run {
fn get_match_3_d_run(&self, input: Match3DRunGetInput,
) {
self.get_match_3_d_run_then(input, |_, _| {});
}
fn get_match_3_d_run_then(
&self,
input: Match3DRunGetInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl get_match_3_d_run for super::RemoteProcedures {
fn get_match_3_d_run_then(
&self,
input: Match3DRunGetInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DRunProcedureResult>(
"get_match_3_d_run",
GetMatch3DRunArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_work_get_input_type::Match3DWorkGetInput;
use super::match_3_d_work_procedure_result_type::Match3DWorkProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct GetMatch3DWorkDetailArgs {
pub input: Match3DWorkGetInput,
}
impl __sdk::InModule for GetMatch3DWorkDetailArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `get_match_3_d_work_detail`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait get_match_3_d_work_detail {
fn get_match_3_d_work_detail(&self, input: Match3DWorkGetInput,
) {
self.get_match_3_d_work_detail_then(input, |_, _| {});
}
fn get_match_3_d_work_detail_then(
&self,
input: Match3DWorkGetInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl get_match_3_d_work_detail for super::RemoteProcedures {
fn get_match_3_d_work_detail_then(
&self,
input: Match3DWorkGetInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DWorkProcedureResult>(
"get_match_3_d_work_detail",
GetMatch3DWorkDetailArgs { input, },
__callback,
);
}
}

View File

@@ -9,8 +9,8 @@ use spacetimedb_sdk::__codegen::{
__ws,
};
use super::puzzle_work_get_input_type::PuzzleWorkGetInput;
use super::puzzle_work_procedure_result_type::PuzzleWorkProcedureResult;
use super::puzzle_work_get_input_type::PuzzleWorkGetInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]

View File

@@ -9,8 +9,8 @@ use spacetimedb_sdk::__codegen::{
__ws,
};
use super::puzzle_work_get_input_type::PuzzleWorkGetInput;
use super::puzzle_work_procedure_result_type::PuzzleWorkProcedureResult;
use super::puzzle_work_get_input_type::PuzzleWorkGetInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_works_procedure_result_type::Match3DWorksProcedureResult;
use super::match_3_d_works_list_input_type::Match3DWorksListInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct ListMatch3DWorksArgs {
pub input: Match3DWorksListInput,
}
impl __sdk::InModule for ListMatch3DWorksArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `list_match_3_d_works`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait list_match_3_d_works {
fn list_match_3_d_works(&self, input: Match3DWorksListInput,
) {
self.list_match_3_d_works_then(input, |_, _| {});
}
fn list_match_3_d_works_then(
&self,
input: Match3DWorksListInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorksProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl list_match_3_d_works for super::RemoteProcedures {
fn list_match_3_d_works_then(
&self,
input: Match3DWorksListInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorksProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DWorksProcedureResult>(
"list_match_3_d_works",
ListMatch3DWorksArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,31 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentMessageFinalizeInput {
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>,
}
impl __sdk::InModule for Match3DAgentMessageFinalizeInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,77 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentMessageRow {
pub message_id: String,
pub session_id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: __sdk::Timestamp,
}
impl __sdk::InModule for Match3DAgentMessageRow {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Match3DAgentMessageRow`.
///
/// Provides typed access to columns for query building.
pub struct Match3DAgentMessageRowCols {
pub message_id: __sdk::__query_builder::Col<Match3DAgentMessageRow, String>,
pub session_id: __sdk::__query_builder::Col<Match3DAgentMessageRow, String>,
pub role: __sdk::__query_builder::Col<Match3DAgentMessageRow, String>,
pub kind: __sdk::__query_builder::Col<Match3DAgentMessageRow, String>,
pub text: __sdk::__query_builder::Col<Match3DAgentMessageRow, String>,
pub created_at: __sdk::__query_builder::Col<Match3DAgentMessageRow, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for Match3DAgentMessageRow {
type Cols = Match3DAgentMessageRowCols;
fn cols(table_name: &'static str) -> Self::Cols {
Match3DAgentMessageRowCols {
message_id: __sdk::__query_builder::Col::new(table_name, "message_id"),
session_id: __sdk::__query_builder::Col::new(table_name, "session_id"),
role: __sdk::__query_builder::Col::new(table_name, "role"),
kind: __sdk::__query_builder::Col::new(table_name, "kind"),
text: __sdk::__query_builder::Col::new(table_name, "text"),
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
}
}
}
/// Indexed column accessor struct for the table `Match3DAgentMessageRow`.
///
/// Provides typed access to indexed columns for query building.
pub struct Match3DAgentMessageRowIxCols {
pub message_id: __sdk::__query_builder::IxCol<Match3DAgentMessageRow, String>,
pub session_id: __sdk::__query_builder::IxCol<Match3DAgentMessageRow, String>,
}
impl __sdk::__query_builder::HasIxCols for Match3DAgentMessageRow {
type IxCols = Match3DAgentMessageRowIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
Match3DAgentMessageRowIxCols {
message_id: __sdk::__query_builder::IxCol::new(table_name, "message_id"),
session_id: __sdk::__query_builder::IxCol::new(table_name, "session_id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Match3DAgentMessageRow {}

View File

@@ -0,0 +1,27 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentMessageSubmitInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub submitted_at_micros: i64,
}
impl __sdk::InModule for Match3DAgentMessageSubmitInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,29 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentSessionCreateInput {
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,
}
impl __sdk::InModule for Match3DAgentSessionCreateInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,24 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentSessionGetInput {
pub session_id: String,
pub owner_user_id: String,
}
impl __sdk::InModule for Match3DAgentSessionGetInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentSessionProcedureResult {
pub ok: bool,
pub session_json: Option::<String>,
pub error_message: Option::<String>,
}
impl __sdk::InModule for Match3DAgentSessionProcedureResult {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,95 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DAgentSessionRow {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub config_json: String,
pub draft_json: String,
pub last_assistant_reply: String,
pub published_profile_id: String,
pub created_at: __sdk::Timestamp,
pub updated_at: __sdk::Timestamp,
}
impl __sdk::InModule for Match3DAgentSessionRow {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Match3DAgentSessionRow`.
///
/// Provides typed access to columns for query building.
pub struct Match3DAgentSessionRowCols {
pub session_id: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub owner_user_id: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub seed_text: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub current_turn: __sdk::__query_builder::Col<Match3DAgentSessionRow, u32>,
pub progress_percent: __sdk::__query_builder::Col<Match3DAgentSessionRow, u32>,
pub stage: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub config_json: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub draft_json: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub last_assistant_reply: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub published_profile_id: __sdk::__query_builder::Col<Match3DAgentSessionRow, String>,
pub created_at: __sdk::__query_builder::Col<Match3DAgentSessionRow, __sdk::Timestamp>,
pub updated_at: __sdk::__query_builder::Col<Match3DAgentSessionRow, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for Match3DAgentSessionRow {
type Cols = Match3DAgentSessionRowCols;
fn cols(table_name: &'static str) -> Self::Cols {
Match3DAgentSessionRowCols {
session_id: __sdk::__query_builder::Col::new(table_name, "session_id"),
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
seed_text: __sdk::__query_builder::Col::new(table_name, "seed_text"),
current_turn: __sdk::__query_builder::Col::new(table_name, "current_turn"),
progress_percent: __sdk::__query_builder::Col::new(table_name, "progress_percent"),
stage: __sdk::__query_builder::Col::new(table_name, "stage"),
config_json: __sdk::__query_builder::Col::new(table_name, "config_json"),
draft_json: __sdk::__query_builder::Col::new(table_name, "draft_json"),
last_assistant_reply: __sdk::__query_builder::Col::new(table_name, "last_assistant_reply"),
published_profile_id: __sdk::__query_builder::Col::new(table_name, "published_profile_id"),
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
}
}
}
/// Indexed column accessor struct for the table `Match3DAgentSessionRow`.
///
/// Provides typed access to indexed columns for query building.
pub struct Match3DAgentSessionRowIxCols {
pub owner_user_id: __sdk::__query_builder::IxCol<Match3DAgentSessionRow, String>,
pub session_id: __sdk::__query_builder::IxCol<Match3DAgentSessionRow, String>,
}
impl __sdk::__query_builder::HasIxCols for Match3DAgentSessionRow {
type IxCols = Match3DAgentSessionRowIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
Match3DAgentSessionRowIxCols {
owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"),
session_id: __sdk::__query_builder::IxCol::new(table_name, "session_id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Match3DAgentSessionRow {}

View File

@@ -0,0 +1,29 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DClickItemProcedureResult {
pub ok: bool,
pub status: String,
pub run_json: Option::<String>,
pub accepted_item_instance_id: Option::<String>,
pub cleared_item_instance_ids: Vec::<String>,
pub failure_reason: Option::<String>,
pub error_message: Option::<String>,
}
impl __sdk::InModule for Match3DClickItemProcedureResult {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,32 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DDraftCompileInput {
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,
}
impl __sdk::InModule for Match3DDraftCompileInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,28 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunClickInput {
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,
}
impl __sdk::InModule for Match3DRunClickInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,24 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunGetInput {
pub run_id: String,
pub owner_user_id: String,
}
impl __sdk::InModule for Match3DRunGetInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunProcedureResult {
pub ok: bool,
pub run_json: Option::<String>,
pub error_message: Option::<String>,
}
impl __sdk::InModule for Match3DRunProcedureResult {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,26 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunRestartInput {
pub source_run_id: String,
pub next_run_id: String,
pub owner_user_id: String,
pub restarted_at_ms: i64,
}
impl __sdk::InModule for Match3DRunRestartInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,26 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunStartInput {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub started_at_ms: i64,
}
impl __sdk::InModule for Match3DRunStartInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunStopInput {
pub run_id: String,
pub owner_user_id: String,
pub stopped_at_ms: i64,
}
impl __sdk::InModule for Match3DRunStopInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRunTimeUpInput {
pub run_id: String,
pub owner_user_id: String,
pub finished_at_ms: i64,
}
impl __sdk::InModule for Match3DRunTimeUpInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,109 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DRuntimeRunRow {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub status: String,
pub snapshot_version: u32,
pub started_at_ms: i64,
pub duration_limit_ms: i64,
pub finished_at_ms: i64,
pub elapsed_ms: i64,
pub clear_count: u32,
pub total_item_count: u32,
pub cleared_item_count: u32,
pub failure_reason: String,
pub snapshot_json: String,
pub created_at: __sdk::Timestamp,
pub updated_at: __sdk::Timestamp,
}
impl __sdk::InModule for Match3DRuntimeRunRow {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Match3DRuntimeRunRow`.
///
/// Provides typed access to columns for query building.
pub struct Match3DRuntimeRunRowCols {
pub run_id: __sdk::__query_builder::Col<Match3DRuntimeRunRow, String>,
pub owner_user_id: __sdk::__query_builder::Col<Match3DRuntimeRunRow, String>,
pub profile_id: __sdk::__query_builder::Col<Match3DRuntimeRunRow, String>,
pub status: __sdk::__query_builder::Col<Match3DRuntimeRunRow, String>,
pub snapshot_version: __sdk::__query_builder::Col<Match3DRuntimeRunRow, u32>,
pub started_at_ms: __sdk::__query_builder::Col<Match3DRuntimeRunRow, i64>,
pub duration_limit_ms: __sdk::__query_builder::Col<Match3DRuntimeRunRow, i64>,
pub finished_at_ms: __sdk::__query_builder::Col<Match3DRuntimeRunRow, i64>,
pub elapsed_ms: __sdk::__query_builder::Col<Match3DRuntimeRunRow, i64>,
pub clear_count: __sdk::__query_builder::Col<Match3DRuntimeRunRow, u32>,
pub total_item_count: __sdk::__query_builder::Col<Match3DRuntimeRunRow, u32>,
pub cleared_item_count: __sdk::__query_builder::Col<Match3DRuntimeRunRow, u32>,
pub failure_reason: __sdk::__query_builder::Col<Match3DRuntimeRunRow, String>,
pub snapshot_json: __sdk::__query_builder::Col<Match3DRuntimeRunRow, String>,
pub created_at: __sdk::__query_builder::Col<Match3DRuntimeRunRow, __sdk::Timestamp>,
pub updated_at: __sdk::__query_builder::Col<Match3DRuntimeRunRow, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for Match3DRuntimeRunRow {
type Cols = Match3DRuntimeRunRowCols;
fn cols(table_name: &'static str) -> Self::Cols {
Match3DRuntimeRunRowCols {
run_id: __sdk::__query_builder::Col::new(table_name, "run_id"),
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"),
status: __sdk::__query_builder::Col::new(table_name, "status"),
snapshot_version: __sdk::__query_builder::Col::new(table_name, "snapshot_version"),
started_at_ms: __sdk::__query_builder::Col::new(table_name, "started_at_ms"),
duration_limit_ms: __sdk::__query_builder::Col::new(table_name, "duration_limit_ms"),
finished_at_ms: __sdk::__query_builder::Col::new(table_name, "finished_at_ms"),
elapsed_ms: __sdk::__query_builder::Col::new(table_name, "elapsed_ms"),
clear_count: __sdk::__query_builder::Col::new(table_name, "clear_count"),
total_item_count: __sdk::__query_builder::Col::new(table_name, "total_item_count"),
cleared_item_count: __sdk::__query_builder::Col::new(table_name, "cleared_item_count"),
failure_reason: __sdk::__query_builder::Col::new(table_name, "failure_reason"),
snapshot_json: __sdk::__query_builder::Col::new(table_name, "snapshot_json"),
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
}
}
}
/// Indexed column accessor struct for the table `Match3DRuntimeRunRow`.
///
/// Provides typed access to indexed columns for query building.
pub struct Match3DRuntimeRunRowIxCols {
pub owner_user_id: __sdk::__query_builder::IxCol<Match3DRuntimeRunRow, String>,
pub profile_id: __sdk::__query_builder::IxCol<Match3DRuntimeRunRow, String>,
pub run_id: __sdk::__query_builder::IxCol<Match3DRuntimeRunRow, String>,
}
impl __sdk::__query_builder::HasIxCols for Match3DRuntimeRunRow {
type IxCols = Match3DRuntimeRunRowIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
Match3DRuntimeRunRowIxCols {
owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"),
profile_id: __sdk::__query_builder::IxCol::new(table_name, "profile_id"),
run_id: __sdk::__query_builder::IxCol::new(table_name, "run_id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Match3DRuntimeRunRow {}

View File

@@ -0,0 +1,24 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorkDeleteInput {
pub profile_id: String,
pub owner_user_id: String,
}
impl __sdk::InModule for Match3DWorkDeleteInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,24 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorkGetInput {
pub profile_id: String,
pub owner_user_id: String,
}
impl __sdk::InModule for Match3DWorkGetInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorkProcedureResult {
pub ok: bool,
pub work_json: Option::<String>,
pub error_message: Option::<String>,
}
impl __sdk::InModule for Match3DWorkProcedureResult {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,112 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorkProfileRow {
pub profile_id: String,
pub owner_user_id: String,
pub source_session_id: String,
pub author_display_name: 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 config_json: String,
pub publication_status: String,
pub play_count: u32,
pub updated_at: __sdk::Timestamp,
pub published_at: Option::<__sdk::Timestamp>,
}
impl __sdk::InModule for Match3DWorkProfileRow {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Match3DWorkProfileRow`.
///
/// Provides typed access to columns for query building.
pub struct Match3DWorkProfileRowCols {
pub profile_id: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub owner_user_id: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub source_session_id: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub author_display_name: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub game_name: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub theme_text: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub summary_text: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub tags_json: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub cover_image_src: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub cover_asset_id: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub clear_count: __sdk::__query_builder::Col<Match3DWorkProfileRow, u32>,
pub difficulty: __sdk::__query_builder::Col<Match3DWorkProfileRow, u32>,
pub config_json: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub publication_status: __sdk::__query_builder::Col<Match3DWorkProfileRow, String>,
pub play_count: __sdk::__query_builder::Col<Match3DWorkProfileRow, u32>,
pub updated_at: __sdk::__query_builder::Col<Match3DWorkProfileRow, __sdk::Timestamp>,
pub published_at: __sdk::__query_builder::Col<Match3DWorkProfileRow, Option::<__sdk::Timestamp>>,
}
impl __sdk::__query_builder::HasCols for Match3DWorkProfileRow {
type Cols = Match3DWorkProfileRowCols;
fn cols(table_name: &'static str) -> Self::Cols {
Match3DWorkProfileRowCols {
profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"),
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
source_session_id: __sdk::__query_builder::Col::new(table_name, "source_session_id"),
author_display_name: __sdk::__query_builder::Col::new(table_name, "author_display_name"),
game_name: __sdk::__query_builder::Col::new(table_name, "game_name"),
theme_text: __sdk::__query_builder::Col::new(table_name, "theme_text"),
summary_text: __sdk::__query_builder::Col::new(table_name, "summary_text"),
tags_json: __sdk::__query_builder::Col::new(table_name, "tags_json"),
cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"),
cover_asset_id: __sdk::__query_builder::Col::new(table_name, "cover_asset_id"),
clear_count: __sdk::__query_builder::Col::new(table_name, "clear_count"),
difficulty: __sdk::__query_builder::Col::new(table_name, "difficulty"),
config_json: __sdk::__query_builder::Col::new(table_name, "config_json"),
publication_status: __sdk::__query_builder::Col::new(table_name, "publication_status"),
play_count: __sdk::__query_builder::Col::new(table_name, "play_count"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
published_at: __sdk::__query_builder::Col::new(table_name, "published_at"),
}
}
}
/// Indexed column accessor struct for the table `Match3DWorkProfileRow`.
///
/// Provides typed access to indexed columns for query building.
pub struct Match3DWorkProfileRowIxCols {
pub owner_user_id: __sdk::__query_builder::IxCol<Match3DWorkProfileRow, String>,
pub profile_id: __sdk::__query_builder::IxCol<Match3DWorkProfileRow, String>,
pub publication_status: __sdk::__query_builder::IxCol<Match3DWorkProfileRow, String>,
}
impl __sdk::__query_builder::HasIxCols for Match3DWorkProfileRow {
type IxCols = Match3DWorkProfileRowIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
Match3DWorkProfileRowIxCols {
owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"),
profile_id: __sdk::__query_builder::IxCol::new(table_name, "profile_id"),
publication_status: __sdk::__query_builder::IxCol::new(table_name, "publication_status"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Match3DWorkProfileRow {}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorkPublishInput {
pub profile_id: String,
pub owner_user_id: String,
pub published_at_micros: i64,
}
impl __sdk::InModule for Match3DWorkPublishInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,33 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorkUpdateInput {
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,
}
impl __sdk::InModule for Match3DWorkUpdateInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,24 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorksListInput {
pub owner_user_id: String,
pub published_only: bool,
}
impl __sdk::InModule for Match3DWorksListInput {
type Module = super::RemoteModule;
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Match3DWorksProcedureResult {
pub ok: bool,
pub items_json: Option::<String>,
pub error_message: Option::<String>,
}
impl __sdk::InModule for Match3DWorksProcedureResult {
type Module = super::RemoteModule;
}

View File

@@ -183,6 +183,31 @@ pub mod inventory_mutation_type;
pub mod inventory_mutation_input_type;
pub mod inventory_slot_type;
pub mod inventory_slot_snapshot_type;
pub mod match_3_d_agent_message_finalize_input_type;
pub mod match_3_d_agent_message_row_type;
pub mod match_3_d_agent_message_submit_input_type;
pub mod match_3_d_agent_session_create_input_type;
pub mod match_3_d_agent_session_get_input_type;
pub mod match_3_d_agent_session_procedure_result_type;
pub mod match_3_d_agent_session_row_type;
pub mod match_3_d_click_item_procedure_result_type;
pub mod match_3_d_draft_compile_input_type;
pub mod match_3_d_run_click_input_type;
pub mod match_3_d_run_get_input_type;
pub mod match_3_d_run_procedure_result_type;
pub mod match_3_d_run_restart_input_type;
pub mod match_3_d_run_start_input_type;
pub mod match_3_d_run_stop_input_type;
pub mod match_3_d_run_time_up_input_type;
pub mod match_3_d_runtime_run_row_type;
pub mod match_3_d_work_delete_input_type;
pub mod match_3_d_work_get_input_type;
pub mod match_3_d_work_procedure_result_type;
pub mod match_3_d_work_profile_row_type;
pub mod match_3_d_work_publish_input_type;
pub mod match_3_d_work_update_input_type;
pub mod match_3_d_works_list_input_type;
pub mod match_3_d_works_procedure_result_type;
pub mod npc_battle_interaction_procedure_result_type;
pub mod npc_battle_interaction_result_type;
pub mod npc_interaction_battle_mode_type;
@@ -245,6 +270,7 @@ pub mod puzzle_select_cover_image_input_type;
pub mod puzzle_work_delete_input_type;
pub mod puzzle_work_get_input_type;
pub mod puzzle_work_like_record_input_type;
pub mod puzzle_work_point_incentive_claim_input_type;
pub mod puzzle_work_procedure_result_type;
pub mod puzzle_work_profile_row_type;
pub mod puzzle_work_remix_input_type;
@@ -414,10 +440,13 @@ pub mod authorize_database_migration_operator_procedure;
pub mod begin_story_session_and_return_procedure;
pub mod bind_asset_object_to_entity_and_return_procedure;
pub mod cancel_ai_task_and_return_procedure;
pub mod claim_puzzle_work_point_incentive_procedure;
pub mod clear_database_migration_import_chunks_procedure;
pub mod clear_platform_browse_history_and_return_procedure;
pub mod click_match_3_d_item_procedure;
pub mod compile_big_fish_draft_procedure;
pub mod compile_custom_world_published_profile_procedure;
pub mod compile_match_3_d_draft_procedure;
pub mod compile_puzzle_agent_draft_procedure;
pub mod complete_ai_stage_and_return_procedure;
pub mod complete_ai_task_and_return_procedure;
@@ -428,11 +457,13 @@ pub mod create_ai_task_and_return_procedure;
pub mod create_battle_state_and_return_procedure;
pub mod create_big_fish_session_procedure;
pub mod create_custom_world_agent_session_procedure;
pub mod create_match_3_d_agent_session_procedure;
pub mod create_profile_recharge_order_and_return_procedure;
pub mod create_puzzle_agent_session_procedure;
pub mod delete_big_fish_work_procedure;
pub mod delete_custom_world_agent_session_procedure;
pub mod delete_custom_world_profile_and_return_procedure;
pub mod delete_match_3_d_work_procedure;
pub mod delete_puzzle_work_procedure;
pub mod delete_runtime_snapshot_and_return_procedure;
pub mod drag_puzzle_piece_or_group_procedure;
@@ -442,7 +473,9 @@ pub mod export_database_migration_to_file_procedure;
pub mod fail_ai_task_and_return_procedure;
pub mod finalize_big_fish_agent_message_turn_procedure;
pub mod finalize_custom_world_agent_message_turn_procedure;
pub mod finalize_match_3_d_agent_message_turn_procedure;
pub mod finalize_puzzle_agent_message_turn_procedure;
pub mod finish_match_3_d_time_up_procedure;
pub mod generate_big_fish_asset_procedure;
pub mod get_auth_store_snapshot_procedure;
pub mod get_battle_state_procedure;
@@ -454,6 +487,9 @@ pub mod get_custom_world_agent_session_procedure;
pub mod get_custom_world_gallery_detail_procedure;
pub mod get_custom_world_gallery_detail_by_code_procedure;
pub mod get_custom_world_library_detail_procedure;
pub mod get_match_3_d_agent_session_procedure;
pub mod get_match_3_d_run_procedure;
pub mod get_match_3_d_work_detail_procedure;
pub mod get_player_progression_or_default_procedure;
pub mod get_profile_dashboard_procedure;
pub mod get_profile_play_stats_procedure;
@@ -478,6 +514,7 @@ pub mod list_big_fish_works_procedure;
pub mod list_custom_world_gallery_entries_procedure;
pub mod list_custom_world_profiles_procedure;
pub mod list_custom_world_works_procedure;
pub mod list_match_3_d_works_procedure;
pub mod list_platform_browse_history_procedure;
pub mod list_profile_save_archives_procedure;
pub mod list_profile_wallet_ledger_procedure;
@@ -486,6 +523,7 @@ pub mod list_puzzle_works_procedure;
pub mod publish_big_fish_game_procedure;
pub mod publish_custom_world_profile_and_return_procedure;
pub mod publish_custom_world_world_procedure;
pub mod publish_match_3_d_work_procedure;
pub mod publish_puzzle_work_procedure;
pub mod put_database_migration_import_chunk_procedure;
pub mod record_big_fish_like_procedure;
@@ -504,18 +542,23 @@ pub mod resolve_npc_battle_interaction_and_return_procedure;
pub mod resolve_npc_interaction_and_return_procedure;
pub mod resolve_npc_social_action_and_return_procedure;
pub mod resolve_treasure_interaction_and_return_procedure;
pub mod restart_match_3_d_run_procedure;
pub mod resume_profile_save_archive_and_return_procedure;
pub mod revoke_database_migration_operator_procedure;
pub mod save_puzzle_form_draft_procedure;
pub mod save_puzzle_generated_images_procedure;
pub mod select_puzzle_cover_image_procedure;
pub mod start_match_3_d_run_procedure;
pub mod start_puzzle_run_procedure;
pub mod stop_match_3_d_run_procedure;
pub mod submit_big_fish_message_procedure;
pub mod submit_custom_world_agent_message_procedure;
pub mod submit_match_3_d_agent_message_procedure;
pub mod submit_puzzle_agent_message_procedure;
pub mod submit_puzzle_leaderboard_entry_procedure;
pub mod swap_puzzle_pieces_procedure;
pub mod unpublish_custom_world_profile_and_return_procedure;
pub mod update_match_3_d_work_procedure;
pub mod update_puzzle_run_pause_procedure;
pub mod update_puzzle_work_procedure;
pub mod upsert_auth_store_snapshot_procedure;
@@ -700,6 +743,31 @@ pub use inventory_mutation_type::InventoryMutation;
pub use inventory_mutation_input_type::InventoryMutationInput;
pub use inventory_slot_type::InventorySlot;
pub use inventory_slot_snapshot_type::InventorySlotSnapshot;
pub use match_3_d_agent_message_finalize_input_type::Match3DAgentMessageFinalizeInput;
pub use match_3_d_agent_message_row_type::Match3DAgentMessageRow;
pub use match_3_d_agent_message_submit_input_type::Match3DAgentMessageSubmitInput;
pub use match_3_d_agent_session_create_input_type::Match3DAgentSessionCreateInput;
pub use match_3_d_agent_session_get_input_type::Match3DAgentSessionGetInput;
pub use match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult;
pub use match_3_d_agent_session_row_type::Match3DAgentSessionRow;
pub use match_3_d_click_item_procedure_result_type::Match3DClickItemProcedureResult;
pub use match_3_d_draft_compile_input_type::Match3DDraftCompileInput;
pub use match_3_d_run_click_input_type::Match3DRunClickInput;
pub use match_3_d_run_get_input_type::Match3DRunGetInput;
pub use match_3_d_run_procedure_result_type::Match3DRunProcedureResult;
pub use match_3_d_run_restart_input_type::Match3DRunRestartInput;
pub use match_3_d_run_start_input_type::Match3DRunStartInput;
pub use match_3_d_run_stop_input_type::Match3DRunStopInput;
pub use match_3_d_run_time_up_input_type::Match3DRunTimeUpInput;
pub use match_3_d_runtime_run_row_type::Match3DRuntimeRunRow;
pub use match_3_d_work_delete_input_type::Match3DWorkDeleteInput;
pub use match_3_d_work_get_input_type::Match3DWorkGetInput;
pub use match_3_d_work_procedure_result_type::Match3DWorkProcedureResult;
pub use match_3_d_work_profile_row_type::Match3DWorkProfileRow;
pub use match_3_d_work_publish_input_type::Match3DWorkPublishInput;
pub use match_3_d_work_update_input_type::Match3DWorkUpdateInput;
pub use match_3_d_works_list_input_type::Match3DWorksListInput;
pub use match_3_d_works_procedure_result_type::Match3DWorksProcedureResult;
pub use npc_battle_interaction_procedure_result_type::NpcBattleInteractionProcedureResult;
pub use npc_battle_interaction_result_type::NpcBattleInteractionResult;
pub use npc_interaction_battle_mode_type::NpcInteractionBattleMode;
@@ -762,6 +830,7 @@ pub use puzzle_select_cover_image_input_type::PuzzleSelectCoverImageInput;
pub use puzzle_work_delete_input_type::PuzzleWorkDeleteInput;
pub use puzzle_work_get_input_type::PuzzleWorkGetInput;
pub use puzzle_work_like_record_input_type::PuzzleWorkLikeRecordInput;
pub use puzzle_work_point_incentive_claim_input_type::PuzzleWorkPointIncentiveClaimInput;
pub use puzzle_work_procedure_result_type::PuzzleWorkProcedureResult;
pub use puzzle_work_profile_row_type::PuzzleWorkProfileRow;
pub use puzzle_work_remix_input_type::PuzzleWorkRemixInput;
@@ -931,10 +1000,13 @@ pub use authorize_database_migration_operator_procedure::authorize_database_migr
pub use begin_story_session_and_return_procedure::begin_story_session_and_return;
pub use bind_asset_object_to_entity_and_return_procedure::bind_asset_object_to_entity_and_return;
pub use cancel_ai_task_and_return_procedure::cancel_ai_task_and_return;
pub use claim_puzzle_work_point_incentive_procedure::claim_puzzle_work_point_incentive;
pub use clear_database_migration_import_chunks_procedure::clear_database_migration_import_chunks;
pub use clear_platform_browse_history_and_return_procedure::clear_platform_browse_history_and_return;
pub use click_match_3_d_item_procedure::click_match_3_d_item;
pub use compile_big_fish_draft_procedure::compile_big_fish_draft;
pub use compile_custom_world_published_profile_procedure::compile_custom_world_published_profile;
pub use compile_match_3_d_draft_procedure::compile_match_3_d_draft;
pub use compile_puzzle_agent_draft_procedure::compile_puzzle_agent_draft;
pub use complete_ai_stage_and_return_procedure::complete_ai_stage_and_return;
pub use complete_ai_task_and_return_procedure::complete_ai_task_and_return;
@@ -945,11 +1017,13 @@ pub use create_ai_task_and_return_procedure::create_ai_task_and_return;
pub use create_battle_state_and_return_procedure::create_battle_state_and_return;
pub use create_big_fish_session_procedure::create_big_fish_session;
pub use create_custom_world_agent_session_procedure::create_custom_world_agent_session;
pub use create_match_3_d_agent_session_procedure::create_match_3_d_agent_session;
pub use create_profile_recharge_order_and_return_procedure::create_profile_recharge_order_and_return;
pub use create_puzzle_agent_session_procedure::create_puzzle_agent_session;
pub use delete_big_fish_work_procedure::delete_big_fish_work;
pub use delete_custom_world_agent_session_procedure::delete_custom_world_agent_session;
pub use delete_custom_world_profile_and_return_procedure::delete_custom_world_profile_and_return;
pub use delete_match_3_d_work_procedure::delete_match_3_d_work;
pub use delete_puzzle_work_procedure::delete_puzzle_work;
pub use delete_runtime_snapshot_and_return_procedure::delete_runtime_snapshot_and_return;
pub use drag_puzzle_piece_or_group_procedure::drag_puzzle_piece_or_group;
@@ -959,7 +1033,9 @@ pub use export_database_migration_to_file_procedure::export_database_migration_t
pub use fail_ai_task_and_return_procedure::fail_ai_task_and_return;
pub use finalize_big_fish_agent_message_turn_procedure::finalize_big_fish_agent_message_turn;
pub use finalize_custom_world_agent_message_turn_procedure::finalize_custom_world_agent_message_turn;
pub use finalize_match_3_d_agent_message_turn_procedure::finalize_match_3_d_agent_message_turn;
pub use finalize_puzzle_agent_message_turn_procedure::finalize_puzzle_agent_message_turn;
pub use finish_match_3_d_time_up_procedure::finish_match_3_d_time_up;
pub use generate_big_fish_asset_procedure::generate_big_fish_asset;
pub use get_auth_store_snapshot_procedure::get_auth_store_snapshot;
pub use get_battle_state_procedure::get_battle_state;
@@ -971,6 +1047,9 @@ pub use get_custom_world_agent_session_procedure::get_custom_world_agent_session
pub use get_custom_world_gallery_detail_procedure::get_custom_world_gallery_detail;
pub use get_custom_world_gallery_detail_by_code_procedure::get_custom_world_gallery_detail_by_code;
pub use get_custom_world_library_detail_procedure::get_custom_world_library_detail;
pub use get_match_3_d_agent_session_procedure::get_match_3_d_agent_session;
pub use get_match_3_d_run_procedure::get_match_3_d_run;
pub use get_match_3_d_work_detail_procedure::get_match_3_d_work_detail;
pub use get_player_progression_or_default_procedure::get_player_progression_or_default;
pub use get_profile_dashboard_procedure::get_profile_dashboard;
pub use get_profile_play_stats_procedure::get_profile_play_stats;
@@ -995,6 +1074,7 @@ pub use list_big_fish_works_procedure::list_big_fish_works;
pub use list_custom_world_gallery_entries_procedure::list_custom_world_gallery_entries;
pub use list_custom_world_profiles_procedure::list_custom_world_profiles;
pub use list_custom_world_works_procedure::list_custom_world_works;
pub use list_match_3_d_works_procedure::list_match_3_d_works;
pub use list_platform_browse_history_procedure::list_platform_browse_history;
pub use list_profile_save_archives_procedure::list_profile_save_archives;
pub use list_profile_wallet_ledger_procedure::list_profile_wallet_ledger;
@@ -1003,6 +1083,7 @@ pub use list_puzzle_works_procedure::list_puzzle_works;
pub use publish_big_fish_game_procedure::publish_big_fish_game;
pub use publish_custom_world_profile_and_return_procedure::publish_custom_world_profile_and_return;
pub use publish_custom_world_world_procedure::publish_custom_world_world;
pub use publish_match_3_d_work_procedure::publish_match_3_d_work;
pub use publish_puzzle_work_procedure::publish_puzzle_work;
pub use put_database_migration_import_chunk_procedure::put_database_migration_import_chunk;
pub use record_big_fish_like_procedure::record_big_fish_like;
@@ -1021,18 +1102,23 @@ pub use resolve_npc_battle_interaction_and_return_procedure::resolve_npc_battle_
pub use resolve_npc_interaction_and_return_procedure::resolve_npc_interaction_and_return;
pub use resolve_npc_social_action_and_return_procedure::resolve_npc_social_action_and_return;
pub use resolve_treasure_interaction_and_return_procedure::resolve_treasure_interaction_and_return;
pub use restart_match_3_d_run_procedure::restart_match_3_d_run;
pub use resume_profile_save_archive_and_return_procedure::resume_profile_save_archive_and_return;
pub use revoke_database_migration_operator_procedure::revoke_database_migration_operator;
pub use save_puzzle_form_draft_procedure::save_puzzle_form_draft;
pub use save_puzzle_generated_images_procedure::save_puzzle_generated_images;
pub use select_puzzle_cover_image_procedure::select_puzzle_cover_image;
pub use start_match_3_d_run_procedure::start_match_3_d_run;
pub use start_puzzle_run_procedure::start_puzzle_run;
pub use stop_match_3_d_run_procedure::stop_match_3_d_run;
pub use submit_big_fish_message_procedure::submit_big_fish_message;
pub use submit_custom_world_agent_message_procedure::submit_custom_world_agent_message;
pub use submit_match_3_d_agent_message_procedure::submit_match_3_d_agent_message;
pub use submit_puzzle_agent_message_procedure::submit_puzzle_agent_message;
pub use submit_puzzle_leaderboard_entry_procedure::submit_puzzle_leaderboard_entry;
pub use swap_puzzle_pieces_procedure::swap_puzzle_pieces;
pub use unpublish_custom_world_profile_and_return_procedure::unpublish_custom_world_profile_and_return;
pub use update_match_3_d_work_procedure::update_match_3_d_work;
pub use update_puzzle_run_pause_procedure::update_puzzle_run_pause;
pub use update_puzzle_work_procedure::update_puzzle_work;
pub use upsert_auth_store_snapshot_procedure::upsert_auth_store_snapshot;

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_work_procedure_result_type::Match3DWorkProcedureResult;
use super::match_3_d_work_publish_input_type::Match3DWorkPublishInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct PublishMatch3DWorkArgs {
pub input: Match3DWorkPublishInput,
}
impl __sdk::InModule for PublishMatch3DWorkArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `publish_match_3_d_work`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait publish_match_3_d_work {
fn publish_match_3_d_work(&self, input: Match3DWorkPublishInput,
) {
self.publish_match_3_d_work_then(input, |_, _| {});
}
fn publish_match_3_d_work_then(
&self,
input: Match3DWorkPublishInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl publish_match_3_d_work for super::RemoteProcedures {
fn publish_match_3_d_work_then(
&self,
input: Match3DWorkPublishInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DWorkProcedureResult>(
"publish_match_3_d_work",
PublishMatch3DWorkArgs { input, },
__callback,
);
}
}

View File

@@ -17,6 +17,7 @@ pub struct PuzzleRunPropInput {
pub owner_user_id: String,
pub prop_kind: String,
pub used_at_micros: i64,
pub spent_points: u64,
}

View File

@@ -0,0 +1,25 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct PuzzleWorkPointIncentiveClaimInput {
pub profile_id: String,
pub owner_user_id: String,
pub claimed_at_micros: i64,
}
impl __sdk::InModule for PuzzleWorkPointIncentiveClaimInput {
type Module = super::RemoteModule;
}

View File

@@ -36,6 +36,8 @@ pub struct PuzzleWorkProfileRow {
pub published_at: Option::<__sdk::Timestamp>,
pub remix_count: u32,
pub like_count: u32,
pub point_incentive_total_half_points: u64,
pub point_incentive_claimed_points: u64,
}
@@ -70,6 +72,8 @@ pub struct PuzzleWorkProfileRowCols {
pub published_at: __sdk::__query_builder::Col<PuzzleWorkProfileRow, Option::<__sdk::Timestamp>>,
pub remix_count: __sdk::__query_builder::Col<PuzzleWorkProfileRow, u32>,
pub like_count: __sdk::__query_builder::Col<PuzzleWorkProfileRow, u32>,
pub point_incentive_total_half_points: __sdk::__query_builder::Col<PuzzleWorkProfileRow, u64>,
pub point_incentive_claimed_points: __sdk::__query_builder::Col<PuzzleWorkProfileRow, u64>,
}
impl __sdk::__query_builder::HasCols for PuzzleWorkProfileRow {
@@ -98,6 +102,8 @@ impl __sdk::__query_builder::HasCols for PuzzleWorkProfileRow {
published_at: __sdk::__query_builder::Col::new(table_name, "published_at"),
remix_count: __sdk::__query_builder::Col::new(table_name, "remix_count"),
like_count: __sdk::__query_builder::Col::new(table_name, "like_count"),
point_incentive_total_half_points: __sdk::__query_builder::Col::new(table_name, "point_incentive_total_half_points"),
point_incentive_claimed_points: __sdk::__query_builder::Col::new(table_name, "point_incentive_claimed_points"),
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_run_procedure_result_type::Match3DRunProcedureResult;
use super::match_3_d_run_restart_input_type::Match3DRunRestartInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct RestartMatch3DRunArgs {
pub input: Match3DRunRestartInput,
}
impl __sdk::InModule for RestartMatch3DRunArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `restart_match_3_d_run`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait restart_match_3_d_run {
fn restart_match_3_d_run(&self, input: Match3DRunRestartInput,
) {
self.restart_match_3_d_run_then(input, |_, _| {});
}
fn restart_match_3_d_run_then(
&self,
input: Match3DRunRestartInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl restart_match_3_d_run for super::RemoteProcedures {
fn restart_match_3_d_run_then(
&self,
input: Match3DRunRestartInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DRunProcedureResult>(
"restart_match_3_d_run",
RestartMatch3DRunArgs { input, },
__callback,
);
}
}

View File

@@ -27,6 +27,8 @@ pub enum RuntimeProfileWalletLedgerSourceType {
RedeemCodeReward,
PuzzleAuthorIncentiveClaim,
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_run_procedure_result_type::Match3DRunProcedureResult;
use super::match_3_d_run_start_input_type::Match3DRunStartInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct StartMatch3DRunArgs {
pub input: Match3DRunStartInput,
}
impl __sdk::InModule for StartMatch3DRunArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `start_match_3_d_run`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait start_match_3_d_run {
fn start_match_3_d_run(&self, input: Match3DRunStartInput,
) {
self.start_match_3_d_run_then(input, |_, _| {});
}
fn start_match_3_d_run_then(
&self,
input: Match3DRunStartInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl start_match_3_d_run for super::RemoteProcedures {
fn start_match_3_d_run_then(
&self,
input: Match3DRunStartInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DRunProcedureResult>(
"start_match_3_d_run",
StartMatch3DRunArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_run_procedure_result_type::Match3DRunProcedureResult;
use super::match_3_d_run_stop_input_type::Match3DRunStopInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct StopMatch3DRunArgs {
pub input: Match3DRunStopInput,
}
impl __sdk::InModule for StopMatch3DRunArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `stop_match_3_d_run`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait stop_match_3_d_run {
fn stop_match_3_d_run(&self, input: Match3DRunStopInput,
) {
self.stop_match_3_d_run_then(input, |_, _| {});
}
fn stop_match_3_d_run_then(
&self,
input: Match3DRunStopInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl stop_match_3_d_run for super::RemoteProcedures {
fn stop_match_3_d_run_then(
&self,
input: Match3DRunStopInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DRunProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DRunProcedureResult>(
"stop_match_3_d_run",
StopMatch3DRunArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult;
use super::match_3_d_agent_message_submit_input_type::Match3DAgentMessageSubmitInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct SubmitMatch3DAgentMessageArgs {
pub input: Match3DAgentMessageSubmitInput,
}
impl __sdk::InModule for SubmitMatch3DAgentMessageArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `submit_match_3_d_agent_message`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait submit_match_3_d_agent_message {
fn submit_match_3_d_agent_message(&self, input: Match3DAgentMessageSubmitInput,
) {
self.submit_match_3_d_agent_message_then(input, |_, _| {});
}
fn submit_match_3_d_agent_message_then(
&self,
input: Match3DAgentMessageSubmitInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl submit_match_3_d_agent_message for super::RemoteProcedures {
fn submit_match_3_d_agent_message_then(
&self,
input: Match3DAgentMessageSubmitInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DAgentSessionProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DAgentSessionProcedureResult>(
"submit_match_3_d_agent_message",
SubmitMatch3DAgentMessageArgs { input, },
__callback,
);
}
}

View File

@@ -0,0 +1,58 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};
use super::match_3_d_work_procedure_result_type::Match3DWorkProcedureResult;
use super::match_3_d_work_update_input_type::Match3DWorkUpdateInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct UpdateMatch3DWorkArgs {
pub input: Match3DWorkUpdateInput,
}
impl __sdk::InModule for UpdateMatch3DWorkArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `update_match_3_d_work`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait update_match_3_d_work {
fn update_match_3_d_work(&self, input: Match3DWorkUpdateInput,
) {
self.update_match_3_d_work_then(input, |_, _| {});
}
fn update_match_3_d_work_then(
&self,
input: Match3DWorkUpdateInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
);
}
impl update_match_3_d_work for super::RemoteProcedures {
fn update_match_3_d_work_then(
&self,
input: Match3DWorkUpdateInput,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<Match3DWorkProcedureResult, __sdk::InternalError>) + Send + 'static,
) {
self.imp.invoke_procedure_with_callback::<_, Match3DWorkProcedureResult>(
"update_match_3_d_work",
UpdateMatch3DWorkArgs { input, },
__callback,
);
}
}

View File

@@ -1,5 +1,6 @@
use super::*;
use crate::mapper::*;
use crate::module_bindings::claim_puzzle_work_point_incentive_procedure::claim_puzzle_work_point_incentive;
use crate::module_bindings::delete_puzzle_work_procedure::delete_puzzle_work;
use crate::module_bindings::record_puzzle_work_like_procedure::record_puzzle_work_like;
use crate::module_bindings::remix_puzzle_work_procedure::remix_puzzle_work;
@@ -340,6 +341,29 @@ impl SpacetimeClient {
.await
}
pub async fn claim_puzzle_work_point_incentive(
&self,
input: PuzzleWorkPointIncentiveClaimRecordInput,
) -> Result<PuzzleWorkProfileRecord, SpacetimeClientError> {
let procedure_input = PuzzleWorkPointIncentiveClaimInput {
profile_id: input.profile_id,
owner_user_id: input.owner_user_id,
claimed_at_micros: input.claimed_at_micros,
};
self.call_after_connect(move |connection, sender| {
connection
.procedures()
.claim_puzzle_work_point_incentive_then(procedure_input, move |_, result| {
let mapped = result
.map_err(|error| SpacetimeClientError::Procedure(error.to_string()))
.and_then(map_puzzle_work_procedure_result);
send_once(&sender, mapped);
});
})
.await
}
pub async fn list_puzzle_gallery(
&self,
) -> Result<Vec<PuzzleWorkProfileRecord>, SpacetimeClientError> {
@@ -586,6 +610,7 @@ impl SpacetimeClient {
owner_user_id: input.owner_user_id,
prop_kind: input.prop_kind,
used_at_micros: input.used_at_micros,
spent_points: input.spent_points,
};
self.call_after_connect(move |connection, sender| {

View File

@@ -1145,6 +1145,12 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
object
.entry("like_count".to_string())
.or_insert_with(|| serde_json::Value::from(0));
object
.entry("point_incentive_total_half_points".to_string())
.or_insert_with(|| serde_json::Value::from(0));
object
.entry("point_incentive_claimed_points".to_string())
.or_insert_with(|| serde_json::Value::from(0));
// 中文注释:拼图多关卡字段晚于旧作品表加入,旧迁移包留空并由读取层补出首关。
object
.entry("levels_json".to_string())

View File

@@ -1,8 +1,8 @@
use crate::runtime::{
ProfilePlayedWorkUpsertInput, PublicWorkLikeRecordInput, PublicWorkPlayRecordInput,
ProfileSaveArchiveUpsertInput,
add_profile_observed_play_time, count_recent_public_work_plays, record_public_work_like,
record_public_work_play, upsert_profile_played_work, upsert_profile_save_archive,
ProfilePlayedWorkUpsertInput, ProfileSaveArchiveUpsertInput, PublicWorkLikeRecordInput,
PublicWorkPlayRecordInput, add_profile_observed_play_time, count_recent_public_work_plays,
grant_profile_wallet_points, record_public_work_like, record_public_work_play,
upsert_profile_played_work, upsert_profile_save_archive,
};
use module_puzzle::{
PUZZLE_MAX_TAG_COUNT, PUZZLE_NEXT_LEVEL_MODE_NONE, PUZZLE_NEXT_LEVEL_MODE_SAME_WORK,
@@ -16,19 +16,23 @@ use module_puzzle::{
PuzzleRunNextLevelInput, PuzzleRunPauseInput, PuzzleRunProcedureResult, PuzzleRunPropInput,
PuzzleRunSnapshot, PuzzleRunStartInput, PuzzleRunSwapInput, PuzzleRuntimeLevelStatus,
PuzzleSelectCoverImageInput, PuzzleWorkDeleteInput, PuzzleWorkGetInput,
PuzzleWorkLikeRecordInput as PuzzleWorkLikeInput, PuzzleWorkProcedureResult, PuzzleWorkProfile,
PuzzleWorkRemixInput, PuzzleWorkUpsertInput, PuzzleWorksListInput, PuzzleWorksProcedureResult,
apply_publish_overrides_to_draft, apply_selected_candidate, build_form_draft_from_seed,
build_result_preview, compile_result_draft_from_seed, create_work_profile, infer_anchor_pack,
normalize_puzzle_draft, normalize_puzzle_levels, normalize_theme_tags, publish_work_profile,
replace_puzzle_level, resolve_puzzle_grid_size, select_next_profiles,
selected_profile_level_after_runtime_level, selected_puzzle_level, tag_similarity_score,
PuzzleWorkLikeRecordInput as PuzzleWorkLikeInput, PuzzleWorkPointIncentiveClaimInput,
PuzzleWorkProcedureResult, PuzzleWorkProfile, PuzzleWorkRemixInput, PuzzleWorkUpsertInput,
PuzzleWorksListInput, PuzzleWorksProcedureResult, apply_publish_overrides_to_draft,
apply_selected_candidate, build_form_draft_from_seed, build_result_preview,
compile_result_draft_from_seed, create_work_profile, infer_anchor_pack, normalize_puzzle_draft,
normalize_puzzle_levels, normalize_theme_tags, publish_work_profile, replace_puzzle_level,
select_next_profiles, selected_profile_level_after_runtime_level, selected_puzzle_level,
tag_similarity_score,
};
use module_runtime::RuntimeProfileWalletLedgerSourceType;
use serde_json::from_str as json_from_str;
use serde_json::json;
use serde_json::to_string as json_to_string;
use spacetimedb::{ProcedureContext, Table, Timestamp, TxContext};
const PUZZLE_POINT_INCENTIVE_DEFAULT_U64: u64 = 0;
/// 拼图 Agent session 真相表。
/// 当前只保存结构化字段与 JSON 草稿,不提前拆出更多编辑态子表。
#[spacetimedb::table(
@@ -98,6 +102,10 @@ pub struct PuzzleWorkProfileRow {
remix_count: u32,
#[default(0)]
like_count: u32,
#[default(PUZZLE_POINT_INCENTIVE_DEFAULT_U64)]
point_incentive_total_half_points: u64,
#[default(PUZZLE_POINT_INCENTIVE_DEFAULT_U64)]
point_incentive_claimed_points: u64,
}
/// 运行态 run 快照表。
@@ -595,6 +603,25 @@ pub fn use_puzzle_runtime_prop(
}
}
#[spacetimedb::procedure]
pub fn claim_puzzle_work_point_incentive(
ctx: &mut ProcedureContext,
input: PuzzleWorkPointIncentiveClaimInput,
) -> PuzzleWorkProcedureResult {
match ctx.try_with_tx(|tx| claim_puzzle_work_point_incentive_tx(tx, input.clone())) {
Ok(item) => PuzzleWorkProcedureResult {
ok: true,
item_json: Some(serialize_json(&item)),
error_message: None,
},
Err(message) => PuzzleWorkProcedureResult {
ok: false,
item_json: None,
error_message: Some(message),
},
}
}
#[spacetimedb::procedure]
pub fn submit_puzzle_leaderboard_entry(
ctx: &mut ProcedureContext,
@@ -1186,6 +1213,8 @@ fn update_puzzle_work_tx(
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count,
point_incentive_total_half_points: row.point_incentive_total_half_points,
point_incentive_claimed_points: row.point_incentive_claimed_points,
anchor_pack_json: row.anchor_pack_json.clone(),
publish_ready: build_result_preview(&preview_draft, Some(&row.author_display_name))
.publish_ready,
@@ -1341,6 +1370,8 @@ fn record_puzzle_work_like_tx(
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count.saturating_add(1),
point_incentive_total_half_points: row.point_incentive_total_half_points,
point_incentive_claimed_points: row.point_incentive_claimed_points,
anchor_pack_json: row.anchor_pack_json.clone(),
publish_ready: row.publish_ready,
created_at: row.created_at,
@@ -1427,6 +1458,8 @@ fn remix_puzzle_work_tx(
play_count: source.play_count,
remix_count: source.remix_count.saturating_add(1),
like_count: source.like_count,
point_incentive_total_half_points: source.point_incentive_total_half_points,
point_incentive_claimed_points: source.point_incentive_claimed_points,
anchor_pack_json: source.anchor_pack_json.clone(),
publish_ready: source.publish_ready,
created_at: source.created_at,
@@ -1492,6 +1525,8 @@ fn remix_puzzle_work_tx(
play_count: 0,
remix_count: 0,
like_count: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
anchor_pack_json: serialize_json(&source_profile.anchor_pack),
publish_ready: true,
created_at: remixed_at,
@@ -1531,13 +1566,20 @@ fn start_puzzle_run_tx(
return Err("入口拼图作品未发布".to_string());
}
let mut entry_profile = build_puzzle_work_profile_from_row(&entry_profile_row)?;
let mut cleared_level_count = 0;
if let Some(level) = selected_profile_level(&entry_profile, input.level_id.as_deref())? {
cleared_level_count =
module_puzzle::resolve_restart_cleared_level_count(&entry_profile, &level.level_id);
entry_profile = profile_for_single_level(&entry_profile, &level);
}
let started_at_ms = micros_to_millis(input.started_at_micros);
let mut run =
module_puzzle::start_run_at(input.run_id.clone(), &entry_profile, 0, started_at_ms)
.map_err(|error| error.to_string())?;
let mut run = module_puzzle::start_run_at(
input.run_id.clone(),
&entry_profile,
cleared_level_count,
started_at_ms,
)
.map_err(|error| error.to_string())?;
let current_grid_size = run.current_grid_size;
let current_profile_id = entry_profile.profile_id.clone();
hydrate_puzzle_leaderboard_entries(
@@ -1682,26 +1724,40 @@ fn advance_puzzle_next_level_tx(
.find(&current_level.profile_id)
.ok_or_else(|| "当前拼图作品不存在".to_string())?;
let current_profile = build_puzzle_work_profile_from_row(&current_profile_row)?;
let next_profile = selected_profile_level_after_runtime_level(&current_profile, current_level)
.map(|level| profile_for_single_level(&current_profile, &level))
.or_else(|| {
let candidates = list_published_puzzle_profiles(ctx).ok()?;
select_next_profiles(
&current_profile,
&current_run.played_profile_ids,
&candidates,
1,
)
.into_iter()
.next()
.cloned()
})
let same_work_next_profile =
selected_profile_level_after_runtime_level(&current_profile, current_level)
.map(|level| profile_for_single_level(&current_profile, &level));
let similar_work_next_profile = if same_work_next_profile.is_none() {
let candidates = list_published_puzzle_profiles(ctx)?;
select_next_profiles(
&current_profile,
&current_run.played_profile_ids,
&candidates,
1,
)
.into_iter()
.next()
.cloned()
} else {
None
};
let next_profile = same_work_next_profile
.as_ref()
.or(similar_work_next_profile.as_ref())
.ok_or_else(|| "没有可用的下一关候选".to_string())?;
let mut next_run = module_puzzle::advance_next_level_at(
&current_run,
&next_profile,
micros_to_millis(input.advanced_at_micros),
)
let mut next_run = if same_work_next_profile.is_some() {
module_puzzle::advance_next_level_at(
&current_run,
next_profile,
micros_to_millis(input.advanced_at_micros),
)
} else {
module_puzzle::advance_to_new_work_first_level_at(
&current_run,
next_profile,
micros_to_millis(input.advanced_at_micros),
)
}
.map_err(|error| error.to_string())?;
let next_grid_size = next_run.current_grid_size;
let next_profile_id = next_profile.profile_id.clone();
@@ -1805,6 +1861,19 @@ fn use_puzzle_runtime_prop_tx(
};
let mut hydrated_run = next_run;
refresh_next_level_handoff(ctx, &mut hydrated_run)?;
if let Some(profile_id) = hydrated_run
.current_level
.as_ref()
.map(|level| level.profile_id.clone())
{
accrue_puzzle_point_incentive(
ctx,
&profile_id,
&input.owner_user_id,
input.spent_points,
input.used_at_micros,
)?;
}
replace_puzzle_runtime_run(ctx, &row, &hydrated_run, input.used_at_micros);
if let Some((profile_id, grid_size)) = hydrated_run
.current_level
@@ -1822,6 +1891,86 @@ fn use_puzzle_runtime_prop_tx(
Ok(hydrated_run)
}
fn claim_puzzle_work_point_incentive_tx(
ctx: &TxContext,
input: PuzzleWorkPointIncentiveClaimInput,
) -> Result<PuzzleWorkProfile, String> {
let profile_id = input.profile_id.trim();
let owner_user_id = input.owner_user_id.trim();
if profile_id.is_empty() || owner_user_id.is_empty() {
return Err("拼图积分激励参数不能为空".to_string());
}
let row = ctx
.db
.puzzle_work_profile()
.profile_id()
.find(&profile_id.to_string())
.ok_or_else(|| "拼图作品不存在".to_string())?;
if row.owner_user_id != owner_user_id {
return Err("无权领取该作品的积分激励".to_string());
}
let claimable_points = puzzle_point_incentive_claimable_points(
row.point_incentive_total_half_points,
row.point_incentive_claimed_points,
);
if claimable_points == 0 {
return Err("暂无可领取积分激励".to_string());
}
let claimed_at = Timestamp::from_micros_since_unix_epoch(input.claimed_at_micros);
let next_row = PuzzleWorkProfileRow {
profile_id: row.profile_id.clone(),
work_id: row.work_id.clone(),
owner_user_id: row.owner_user_id.clone(),
source_session_id: row.source_session_id.clone(),
author_display_name: row.author_display_name.clone(),
work_title: row.work_title.clone(),
work_description: row.work_description.clone(),
level_name: row.level_name.clone(),
summary: row.summary.clone(),
theme_tags_json: row.theme_tags_json.clone(),
cover_image_src: row.cover_image_src.clone(),
cover_asset_id: row.cover_asset_id.clone(),
levels_json: row.levels_json.clone(),
publication_status: row.publication_status,
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count,
point_incentive_total_half_points: row.point_incentive_total_half_points,
point_incentive_claimed_points: row
.point_incentive_claimed_points
.saturating_add(claimable_points),
anchor_pack_json: row.anchor_pack_json.clone(),
publish_ready: row.publish_ready,
created_at: row.created_at,
updated_at: claimed_at,
published_at: row.published_at,
};
replace_puzzle_work_profile(ctx, &row, next_row);
grant_profile_wallet_points(
ctx,
owner_user_id,
claimable_points,
RuntimeProfileWalletLedgerSourceType::PuzzleAuthorIncentiveClaim,
&format!(
"puzzle_author_incentive_claim:{}:{}:{}",
profile_id, owner_user_id, input.claimed_at_micros
),
claimed_at,
)?;
let updated = ctx
.db
.puzzle_work_profile()
.profile_id()
.find(&profile_id.to_string())
.ok_or_else(|| "拼图积分激励领取更新失败".to_string())?;
build_puzzle_work_profile_from_row(&updated)
}
fn submit_puzzle_leaderboard_entry_tx(
ctx: &TxContext,
input: PuzzleLeaderboardSubmitInput,
@@ -1835,7 +1984,7 @@ fn submit_puzzle_leaderboard_entry_tx(
if input.profile_id.trim().is_empty() {
return Err("提交成绩的拼图作品不能为空".to_string());
}
if input.grid_size != 3 && input.grid_size != 4 {
if !module_puzzle::is_supported_puzzle_grid_size(input.grid_size) {
return Err("提交成绩的网格规格无效".to_string());
}
let matches_service_level =
@@ -2002,6 +2151,8 @@ fn build_puzzle_work_profile_from_row_without_recent_count(
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count,
point_incentive_total_half_points: row.point_incentive_total_half_points,
point_incentive_claimed_points: row.point_incentive_claimed_points,
recent_play_count_7d: 0,
publish_ready: row.publish_ready,
anchor_pack: deserialize_anchor_pack(&row.anchor_pack_json)?,
@@ -2108,6 +2259,8 @@ fn upsert_puzzle_draft_work_profile(
profile.play_count = existing.play_count;
profile.remix_count = existing.remix_count;
profile.like_count = existing.like_count;
profile.point_incentive_total_half_points = existing.point_incentive_total_half_points;
profile.point_incentive_claimed_points = existing.point_incentive_claimed_points;
return upsert_puzzle_work_profile(ctx, profile);
}
let profile = create_work_profile(
@@ -2286,6 +2439,12 @@ fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Re
play_count: existing.play_count.max(profile.play_count),
remix_count: existing.remix_count.max(profile.remix_count),
like_count: existing.like_count.max(profile.like_count),
point_incentive_total_half_points: existing
.point_incentive_total_half_points
.max(profile.point_incentive_total_half_points),
point_incentive_claimed_points: existing
.point_incentive_claimed_points
.max(profile.point_incentive_claimed_points),
anchor_pack_json: serialize_json(&profile.anchor_pack),
publish_ready: profile.publish_ready,
created_at: existing.created_at,
@@ -2316,6 +2475,8 @@ fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Re
play_count: profile.play_count,
remix_count: profile.remix_count,
like_count: profile.like_count,
point_incentive_total_half_points: profile.point_incentive_total_half_points,
point_incentive_claimed_points: profile.point_incentive_claimed_points,
anchor_pack_json: serialize_json(&profile.anchor_pack),
publish_ready: profile.publish_ready,
created_at: Timestamp::from_micros_since_unix_epoch(profile.updated_at_micros),
@@ -2375,7 +2536,7 @@ fn replace_puzzle_runtime_run(
.unwrap_or_else(|| current.current_profile_id.clone()),
cleared_level_count: run.cleared_level_count,
current_level_index: run.current_level_index,
current_grid_size: resolve_puzzle_grid_size(run.cleared_level_count),
current_grid_size: run.current_grid_size,
played_profile_ids_json: serialize_json(&run.played_profile_ids),
previous_level_tags_json: serialize_json(&run.previous_level_tags),
snapshot_json: serialize_json(run),
@@ -2403,16 +2564,17 @@ fn upsert_puzzle_profile_save_archive(
return Ok(());
};
let world_key = format!("puzzle:{}", run.entry_profile_id);
let target = resolve_puzzle_archive_target(ctx, run, current_level)?;
// 中文注释:拼图存档只保存恢复入口所需的最小运行态索引,棋盘真相继续放在 puzzle_runtime_run。
let game_state_json = json_to_string(&json!({
"runtimeKind": "puzzle",
"runId": run.run_id,
"entryProfileId": run.entry_profile_id,
"currentProfileId": current_level.profile_id,
"currentLevelIndex": current_level.level_index,
"currentLevelId": current_level.level_id,
"status": current_level.status.as_str(),
"currentProfileId": target.profile_id,
"currentLevelIndex": target.level_index,
"currentLevelId": target.level_id,
"status": target.status.as_str(),
}))
.unwrap_or_else(|_| "{}".to_string());
@@ -2421,13 +2583,13 @@ fn upsert_puzzle_profile_save_archive(
ProfileSaveArchiveUpsertInput {
user_id: user_id.to_string(),
world_key,
owner_user_id: resolve_puzzle_current_owner_user_id(ctx, &current_level.profile_id),
owner_user_id: target.owner_user_id,
profile_id: Some(run.entry_profile_id.clone()),
world_type: Some("PUZZLE".to_string()),
world_name: current_level.level_name.clone(),
subtitle: format!("第 {} 关", current_level.level_index),
summary_text: puzzle_archive_summary_text(current_level.status),
cover_image_src: current_level.cover_image_src.clone(),
world_name: target.level_name,
subtitle: format!("第 {} 关", target.level_index),
summary_text: puzzle_archive_summary_text(target.status),
cover_image_src: target.cover_image_src,
bottom_tab: "puzzle".to_string(),
game_state_json,
current_story_json: None,
@@ -2436,6 +2598,88 @@ fn upsert_puzzle_profile_save_archive(
)
}
struct PuzzleArchiveTarget {
profile_id: String,
level_index: u32,
level_id: Option<String>,
level_name: String,
status: PuzzleRuntimeLevelStatus,
cover_image_src: Option<String>,
owner_user_id: Option<String>,
}
fn resolve_puzzle_archive_target(
ctx: &TxContext,
run: &PuzzleRunSnapshot,
current_level: &module_puzzle::PuzzleRuntimeLevelSnapshot,
) -> Result<PuzzleArchiveTarget, String> {
let owner_user_id = resolve_puzzle_current_owner_user_id(ctx, &current_level.profile_id);
if current_level.status != PuzzleRuntimeLevelStatus::Cleared {
return Ok(PuzzleArchiveTarget {
profile_id: current_level.profile_id.clone(),
level_index: current_level.level_index,
level_id: current_level.level_id.clone(),
level_name: current_level.level_name.clone(),
status: current_level.status,
cover_image_src: current_level.cover_image_src.clone(),
owner_user_id,
});
}
let Some(next_level_id) = run
.next_level_id
.as_deref()
.filter(|value| !value.trim().is_empty())
else {
return Ok(PuzzleArchiveTarget {
profile_id: current_level.profile_id.clone(),
level_index: current_level.level_index,
level_id: current_level.level_id.clone(),
level_name: current_level.level_name.clone(),
status: current_level.status,
cover_image_src: current_level.cover_image_src.clone(),
owner_user_id,
});
};
if run.next_level_profile_id.as_deref() != Some(current_level.profile_id.as_str())
|| run.next_level_mode != PUZZLE_NEXT_LEVEL_MODE_SAME_WORK
{
return Ok(PuzzleArchiveTarget {
profile_id: current_level.profile_id.clone(),
level_index: current_level.level_index,
level_id: current_level.level_id.clone(),
level_name: current_level.level_name.clone(),
status: current_level.status,
cover_image_src: current_level.cover_image_src.clone(),
owner_user_id,
});
}
let current_profile = build_puzzle_work_profile_from_row(
&ctx.db
.puzzle_work_profile()
.profile_id()
.find(&current_level.profile_id)
.ok_or_else(|| "当前拼图作品不存在".to_string())?,
)?;
let next_level = current_profile
.levels
.iter()
.find(|level| level.level_id == next_level_id)
.cloned()
.ok_or_else(|| "下一关拼图关卡不存在".to_string())?;
Ok(PuzzleArchiveTarget {
profile_id: current_profile.profile_id,
level_index: current_level.level_index.saturating_add(1),
level_id: Some(next_level.level_id),
level_name: next_level.level_name,
status: PuzzleRuntimeLevelStatus::Playing,
cover_image_src: next_level.cover_image_src,
owner_user_id,
})
}
fn resolve_puzzle_current_owner_user_id(ctx: &TxContext, profile_id: &str) -> Option<String> {
ctx.db
.puzzle_work_profile()
@@ -2453,6 +2697,72 @@ fn puzzle_archive_summary_text(status: PuzzleRuntimeLevelStatus) -> String {
.to_string()
}
fn puzzle_point_incentive_claimable_points(total_half_points: u64, claimed_points: u64) -> u64 {
total_half_points
.saturating_div(2)
.saturating_sub(claimed_points)
}
fn accrue_puzzle_point_incentive(
ctx: &TxContext,
profile_id: &str,
player_user_id: &str,
spent_points: u64,
updated_at_micros: i64,
) -> Result<(), String> {
if spent_points == 0 {
return Ok(());
}
let Some(row) = ctx
.db
.puzzle_work_profile()
.profile_id()
.find(&profile_id.to_string())
else {
return Ok(());
};
if row.publication_status != PuzzlePublicationStatus::Published
|| row.owner_user_id == player_user_id
{
return Ok(());
}
replace_puzzle_work_profile(
ctx,
&row,
PuzzleWorkProfileRow {
profile_id: row.profile_id.clone(),
work_id: row.work_id.clone(),
owner_user_id: row.owner_user_id.clone(),
source_session_id: row.source_session_id.clone(),
author_display_name: row.author_display_name.clone(),
work_title: row.work_title.clone(),
work_description: row.work_description.clone(),
level_name: row.level_name.clone(),
summary: row.summary.clone(),
theme_tags_json: row.theme_tags_json.clone(),
cover_image_src: row.cover_image_src.clone(),
cover_asset_id: row.cover_asset_id.clone(),
levels_json: row.levels_json.clone(),
publication_status: row.publication_status,
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count,
point_incentive_total_half_points: row
.point_incentive_total_half_points
.saturating_add(spent_points),
point_incentive_claimed_points: row.point_incentive_claimed_points,
anchor_pack_json: row.anchor_pack_json.clone(),
publish_ready: row.publish_ready,
created_at: row.created_at,
updated_at: Timestamp::from_micros_since_unix_epoch(updated_at_micros),
published_at: row.published_at,
},
);
Ok(())
}
fn increment_puzzle_profile_play_count(
ctx: &TxContext,
row: &PuzzleWorkProfileRow,
@@ -2479,6 +2789,8 @@ fn increment_puzzle_profile_play_count(
play_count: row.play_count.saturating_add(1),
remix_count: row.remix_count,
like_count: row.like_count,
point_incentive_total_half_points: row.point_incentive_total_half_points,
point_incentive_claimed_points: row.point_incentive_claimed_points,
anchor_pack_json: row.anchor_pack_json.clone(),
publish_ready: row.publish_ready,
created_at: row.created_at,
@@ -2841,7 +3153,7 @@ mod tests {
}];
replace_generated_candidate(
&mut draft,
&mut draft.candidates,
vec![PuzzleGeneratedImageCandidate {
candidate_id: "session-1-candidate-2".to_string(),
image_src: "/generated-puzzle-assets/session-1/new/cover.png".to_string(),
@@ -2866,11 +3178,14 @@ mod tests {
owner_user_id: "owner-a".to_string(),
source_session_id: None,
author_display_name: "作者".to_string(),
work_title: "A".to_string(),
work_description: String::new(),
level_name: "A".to_string(),
summary: String::new(),
theme_tags: vec!["雨夜".to_string(), "猫咪".to_string()],
cover_image_src: Some("/a.png".to_string()),
cover_asset_id: Some("asset-a".to_string()),
levels: Vec::new(),
publication_status: PuzzlePublicationStatus::Published,
updated_at_micros: 1,
published_at_micros: Some(1),
@@ -2878,6 +3193,8 @@ mod tests {
recent_play_count_7d: 0,
remix_count: 0,
like_count: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
publish_ready: true,
anchor_pack: empty_anchor_pack(),
};
@@ -2885,10 +3202,13 @@ mod tests {
owner_user_id: "owner-a".to_string(),
profile_id: "profile-b".to_string(),
work_id: "work-b".to_string(),
work_title: "B".to_string(),
work_description: String::new(),
level_name: "B".to_string(),
theme_tags: vec!["雨夜".to_string(), "蒸汽城市".to_string()],
cover_image_src: Some("/b.png".to_string()),
cover_asset_id: Some("asset-b".to_string()),
levels: Vec::new(),
publication_status: PuzzlePublicationStatus::Published,
updated_at_micros: 2,
published_at_micros: Some(2),
@@ -2896,6 +3216,8 @@ mod tests {
recent_play_count_7d: 0,
remix_count: 0,
like_count: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
publish_ready: true,
anchor_pack: empty_anchor_pack(),
source_session_id: None,

View File

@@ -2120,6 +2120,24 @@ fn apply_profile_wallet_delta(
)
}
pub(crate) fn grant_profile_wallet_points(
ctx: &ReducerContext,
user_id: &str,
amount_delta: u64,
source_type: RuntimeProfileWalletLedgerSourceType,
ledger_id: &str,
created_at: Timestamp,
) -> Result<u64, String> {
apply_profile_wallet_delta(
ctx,
user_id,
amount_delta,
source_type,
ledger_id,
created_at,
)
}
fn apply_profile_wallet_adjustment(
ctx: &ReducerContext,
input: RuntimeProfileWalletAdjustmentInput,