feat(runtime): prepare safe context checkpoints
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -515,6 +515,7 @@ impl DemoAppState {
|
||||
.get("nana.ending.returned_before_dawn")
|
||||
.copied()
|
||||
.unwrap_or(false)
|
||||
&& request.intent != TurnIntent::Regenerate
|
||||
{
|
||||
return Err(CommandError::invalid_input(
|
||||
"这一夜的故事已经结束;可从回溯中选择另一条线路。",
|
||||
@@ -965,7 +966,16 @@ impl TurnPlanProvider for DemoPlanProvider {
|
||||
)
|
||||
});
|
||||
|
||||
let (beats, delta, suggestions, can_continue) = if accepted_promise {
|
||||
let (beats, delta, suggestions, can_continue) = if request.intent == TurnIntent::Regenerate
|
||||
{
|
||||
let (beats, delta) = regular_turn(request);
|
||||
let ending = state
|
||||
.world_flags
|
||||
.get("nana.ending.returned_before_dawn")
|
||||
.copied()
|
||||
.unwrap_or(false);
|
||||
(beats, delta, Vec::new(), !ending)
|
||||
} else if accepted_promise {
|
||||
let (beats, delta) = promise_turn(request, &committed_node_id);
|
||||
(beats, delta, investigation_suggestions(), true)
|
||||
} else if state
|
||||
@@ -2188,6 +2198,47 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_ending_can_be_regenerated(app: &DemoAppState, ending_node_id: &str) {
|
||||
let rejected = app
|
||||
.submit_turn(&continue_request(
|
||||
DEMO_BRANCH_ID,
|
||||
ending_node_id,
|
||||
"action_after_ending",
|
||||
))
|
||||
.expect_err("ending is terminal");
|
||||
assert_eq!(rejected.code, "invalid_input");
|
||||
assert_eq!(
|
||||
app.store
|
||||
.load_state(DEMO_STORY_ID, DEMO_BRANCH_ID)
|
||||
.expect("unchanged ending")
|
||||
.current_node,
|
||||
ending_node_id
|
||||
);
|
||||
|
||||
let mut regenerate =
|
||||
continue_request(DEMO_BRANCH_ID, ending_node_id, "action_regenerate_ending");
|
||||
regenerate.intent = TurnIntent::Regenerate;
|
||||
regenerate.input = "this request text must not replace the original action".to_owned();
|
||||
let regenerated = app
|
||||
.submit_turn(®enerate)
|
||||
.expect("ending presentation can be regenerated");
|
||||
assert_ne!(regenerated.player_view.branch_id, DEMO_BRANCH_ID);
|
||||
assert!(!regenerated.player_view.can_continue);
|
||||
assert_eq!(
|
||||
app.store
|
||||
.load_state(DEMO_STORY_ID, DEMO_BRANCH_ID)
|
||||
.expect("original ending branch retained")
|
||||
.current_node,
|
||||
ending_node_id
|
||||
);
|
||||
let regenerated_node = app
|
||||
.store
|
||||
.load_node(DEMO_STORY_ID, ®enerated.committed_node_id)
|
||||
.expect("regenerated ending node");
|
||||
assert_eq!(regenerated_node.action_id, "action_return_before_dawn");
|
||||
assert!(regenerated_node.user_input.is_empty());
|
||||
}
|
||||
|
||||
fn node(id: &str, parent_id: Option<&str>, ops: Vec<StateOp>) -> StoryNode {
|
||||
StoryNode {
|
||||
id: id.to_owned(),
|
||||
@@ -2312,21 +2363,7 @@ mod tests {
|
||||
.get("nana.ending.returned_before_dawn"),
|
||||
Some(&true)
|
||||
);
|
||||
let rejected = app
|
||||
.submit_turn(&continue_request(
|
||||
DEMO_BRANCH_ID,
|
||||
&returned.committed_node_id,
|
||||
"action_after_ending",
|
||||
))
|
||||
.expect_err("ending is terminal");
|
||||
assert_eq!(rejected.code, "invalid_input");
|
||||
assert_eq!(
|
||||
app.store
|
||||
.load_state(DEMO_STORY_ID, DEMO_BRANCH_ID)
|
||||
.expect("unchanged ending")
|
||||
.current_node,
|
||||
returned.committed_node_id
|
||||
);
|
||||
assert_ending_can_be_regenerated(&app, &returned.committed_node_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||