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]