Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "route-engine-js",
"version": "1.1.0",
"version": "1.2.0",
"description": "A lightweight Visual Novel engine built in JavaScript for creating interactive narrative games with branching storylines",
"repository": {
"type": "git",
Expand Down
9 changes: 2 additions & 7 deletions spec/RouteEngine.rollbackRenderState.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const createProjectData = () => ({
});

describe("RouteEngine rollback render state", () => {
it("restores rollbacked lines directly in their settled end state", () => {
it("restores rollbacked lines directly in their settled end state without transient layered views", () => {
const routeGraphics = {
render: vi.fn(),
};
Expand Down Expand Up @@ -192,12 +192,7 @@ describe("RouteEngine rollback render state", () => {
revealEffect: "none",
},
);
expect(findElementById(rollbackRender.elements, "panel-text")).toMatchObject(
{
type: "text",
content: "Layered panel",
},
);
expect(findElementById(rollbackRender.elements, "panel-text")).toBeNull();
expect(rollbackRender.animations).toEqual([]);
});

Expand Down
221 changes: 191 additions & 30 deletions spec/RouteEngine.systemState.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,106 @@ const createResetStoryAtSectionProjectData = () => ({
},
});

const createSaveLoadRollbackOverlayProjectData = () => ({
screen: {
width: 1920,
height: 1080,
backgroundColor: "#000000",
},
resources: {
layouts: {
saveMenuLayout: {
elements: [],
},
},
sounds: {},
images: {},
videos: {},
sprites: {},
characters: {},
variables: {},
transforms: {},
sectionTransitions: {},
animations: {},
fonts: {},
colors: {},
textStyles: {},
},
story: {
initialSceneId: "scene1",
scenes: {
scene1: {
initialSectionId: "entry",
sections: {
entry: {
lines: [
{
id: "line1",
actions: {},
},
],
},
afterSave: {
lines: [
{
id: "line2",
actions: {},
},
],
},
},
},
},
},
});

const createDialogueUIRollbackProjectData = () => ({
screen: {
width: 1920,
height: 1080,
backgroundColor: "#000000",
},
resources: {
layouts: {},
sounds: {},
images: {},
videos: {},
sprites: {},
characters: {},
variables: {},
transforms: {},
sectionTransitions: {},
animations: {},
fonts: {},
colors: {},
textStyles: {},
},
story: {
initialSceneId: "scene1",
scenes: {
scene1: {
initialSectionId: "section1",
sections: {
section1: {
lines: [
{
id: "line1",
actions: {
hideDialogueUI: {},
},
},
{
id: "line2",
actions: {},
},
],
},
},
},
},
},
});

const createRouteEngineWithInlineEffects = () => {
let engine;
const handlePendingEffects = (pendingEffects) => {
Expand Down Expand Up @@ -337,21 +437,6 @@ describe("RouteEngine selectSystemState", () => {
sectionId: "gameStart",
lineId: "gameLine",
rollbackPolicy: "free",
executedActions: [
{
type: "updateVariable",
payload: {
id: "seedGameScore",
operations: [
{
variableId: "score",
op: "increment",
value: 1,
},
],
},
},
],
},
],
});
Expand Down Expand Up @@ -382,22 +467,98 @@ describe("RouteEngine selectSystemState", () => {
sectionId: "title",
lineId: "titleLine",
rollbackPolicy: "free",
executedActions: [
{
type: "updateVariable",
payload: {
id: "seedTitleScore",
operations: [
{
variableId: "score",
op: "set",
value: 7,
},
],
},
},
],
},
]);
});

it("does not reopen transient layered views when rolling back after load", () => {
const engine = createRouteEngineWithInlineEffects();

engine.init({
initialState: {
projectData: createSaveLoadRollbackOverlayProjectData(),
},
});

engine.handleActions({
pushLayeredView: {
resourceId: "saveMenuLayout",
resourceType: "layout",
},
});
engine.handleActions({
sectionTransition: {
sectionId: "afterSave",
},
saveSlot: {
slotId: 1,
},
});

let state = engine.selectSystemState();
expect(state.global.saveSlots["1"].state.contexts[0].rollback.timeline).toEqual(
[
{
sectionId: "entry",
lineId: "line1",
rollbackPolicy: "free",
},
{
sectionId: "afterSave",
lineId: "line2",
rollbackPolicy: "free",
},
],
);

engine.handleAction("loadSlot", { slotId: 1 });

state = engine.selectSystemState();
expect(state.contexts[0].pointers.read).toMatchObject({
sectionId: "afterSave",
lineId: "line2",
});
expect(state.global.layeredViews).toEqual([]);

engine.handleAction("rollbackByOffset", { offset: -1 });

state = engine.selectSystemState();
expect(state.contexts[0].pointers.read).toEqual({
sectionId: "entry",
lineId: "line1",
});
expect(state.global.layeredViews).toEqual([]);
});

it("does not restore dialogue UI visibility changes authored on the rollback target line", () => {
const engine = createRouteEngineWithInlineEffects();

engine.init({
initialState: {
projectData: createDialogueUIRollbackProjectData(),
},
});

expect(engine.selectSystemState().global.dialogueUIHidden).toBe(true);

engine.handleAction("markLineCompleted", {});
engine.handleAction("nextLine", {});
engine.handleAction("nextLine", {});

let state = engine.selectSystemState();
expect(state.contexts[0].pointers.read).toEqual({
sectionId: "section1",
lineId: "line2",
});
expect(state.global.dialogueUIHidden).toBe(false);

engine.handleAction("rollbackByOffset", { offset: -1 });

state = engine.selectSystemState();
expect(state.contexts[0].pointers.read).toEqual({
sectionId: "section1",
lineId: "line1",
});
expect(state.global.dialogueUIHidden).toBe(false);
});
});
97 changes: 97 additions & 0 deletions spec/system/actions/rollbackByOffset.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -804,3 +804,100 @@ out:
- sectionId: "section1"
lineId: "3"
rollbackPolicy: "free"
---
case: keeps dialogue UI visible when rolling back to a line that hid it on entry
in:
- state:
projectData:
story:
scenes:
scene1:
sections:
section1:
lines:
- id: "1"
actions:
hideDialogueUI: {}
- id: "2"
resources:
variables: {}
global:
isLineCompleted: false
dialogueUIHidden: false
nextLineConfig:
manual:
enabled: true
requireLineCompleted: false
auto:
enabled: false
applyMode: "persistent"
pendingEffects: []
contexts:
- variables: {}
currentPointerMode: "read"
pointers:
read:
sectionId: "section1"
lineId: "2"
history: {}
rollback:
currentIndex: 1
isRestoring: false
replayStartIndex: 0
timeline:
- sectionId: "section1"
lineId: "1"
rollbackPolicy: "free"
- sectionId: "section1"
lineId: "2"
rollbackPolicy: "free"
- offset: -1
out:
projectData:
story:
scenes:
scene1:
sections:
section1:
lines:
- id: "1"
actions:
hideDialogueUI: {}
- id: "2"
resources:
variables: {}
global:
isLineCompleted: true
dialogueUIHidden: false
layeredViews: []
nextLineConfig:
manual:
enabled: true
requireLineCompleted: false
auto:
enabled: false
applyMode: "persistent"
pendingEffects:
- name: "clearNextLineConfigTimer"
- name: "render"
contexts:
- variables: {}
currentPointerMode: "read"
pointers:
read:
sectionId: "section1"
lineId: "1"
history:
sectionId: null
lineId: null
rollback:
currentIndex: 0
isRestoring: false
replayStartIndex: 0
timeline:
- sectionId: "section1"
lineId: "1"
rollbackPolicy: "free"
- sectionId: "section1"
lineId: "2"
rollbackPolicy: "free"
Loading
Loading