Quest architecture

There are no scripts. There is logic: how nodes and variables are arranged in Romergo

When people talk about logic in a game editor, they usually quickly get to scripts. Somewhere the author writes JavaScript, somewhere Python, and somewhere studies the engine's own language. This provides almost unlimited freedom, but along with it comes syntax errors, endless loops, incompatible plugins, and code that is scary to open even for its author after six months.

In Romergo I took a different path. There are no arbitrary scripts inside the quest. You cannot insert a function, make a network request, or gain direct access to the player. Instead, the story is assembled from a limited but sufficiently expressive set: logical nodes, variables, conditions, and effects.

This does not mean that in Romergo you can only make simple branches. You can remember the player's decisions, count points, open and hide options, choose different routes in subsequent chapters, create random events, and build interactive interfaces. It's just that instead of the command 'execute this code,' the author describes 'in this state, this should happen.'

The graph is already a program

The most basic logic of Romergo is right on the chapter map. Scenes are connected by transitions, and special nodes decide where the progression will go next.

To simplify greatly, a small quest looks like this:

Start
  ↓
Conversation with the guard
  ↓
Is there a pass?
  ├─ yes → Closed archive
  └─ no → Bypass

This is already executable logic. Player enters the starting node, displays the scene, reads the history state, and selects the appropriate transition. The author sees the same path as a comprehensible map, not as a set of if, goto, and service identifiers.

![Flow Romergo: Final Accusation passes through Switch and three If / Else to different endings, with the Logic panel open at the bottom](../assets/logic-without-scripts-flow-nodes.webp)

*A fragment of the real Flow in Builder: the Final Accusation scene conveys the decision to Switch, then several If / Else check the state and branch the story to different endings. The open Logic panel at the bottom shows the entire available set: Dead End, Checkpoint, If / Else, Switch, Random, and Teleport.*

There are currently six main nodes in the logic panel:

Start and Finish frame the chapter, regular scenes show the content, and logical nodes turn them into a route. Even at this level, you can assemble a linear story, a branching path, multiple endings, a random event, and a safe return point — without a single line of script.

A variable is the memory of history

One graph is not enough if the history needs to remember what happened earlier. For this, the quest has variables of three simple types:

Each variable has a key, a label understandable to the author, and an initial value. A numeric variable can also have a minimum and maximum described, and a service variable can be hidden from the player's status panel or shown only when a condition is met.

The main thing here is that the variables belong to the entire quest passage, not just a single scene. If in the first chapter the player obtained a pass, in the fourth chapter the same flag can be checked. When moving through the Finish to the next chapter, the state is not reset. It is preserved along with the selected options, applied effects, checkpoint, and current step of randomness.

This results in a simple authorial loop:

the player's choice writes a value
              ↓
the variable stores the state
              ↓
the condition reads it later
              ↓
the graph, scene, or interface reacts

For example, in the first scene, the choice “Show the found photograph” might set portrait_revealed = true. Later, an **If / Else** node will check this flag and open a new conversation scene. Even later, the terminal interface will show an additional entry only to those players who revealed the photograph.

One variable connects three different parts of the quest. You don't need to manually pass the value between scenes or chapters: they all read the same general progress state.

![The variable page route shows the key, name, type, and visibility of the state.](../assets/logic-without-scripts-variables.webp)

*The variable route in one card has a stable key, a clear name, a value type, and state visibility set. Connections with choices and branches are located below on the same page.*

Choices write, branches read

In a typical scene, the most understandable way to change a state is to add an effect to a choice option. In the visual editor, this looks like an assignment:

"Trust Mary" → ally = "mary"
"Go alone"     → ally = "none"

After this, Switch can direct the player to the desired scene based on the value of ally. The variables page separately shows which choices set the variable and which branches read it. It's a small detail, but in a large quest, it replaces the tedious search through dozens of scenes.

![Settings Switch with the Laboratory and Habitat branches, which compare the variable of the selected route](../assets/logic-without-scripts-switch-rules.webp)

*Switch branches are checked from top to bottom. In this example, the Laboratory route triggers at route = lab, and Habitat at route = habitat.*

Conditions support common comparisons:

In a simple case, the author selects a variable, an operator, and a value directly in the If / Else or Switch settings. For more complex logic, the general runtime understands groups of **all conditions**, **any condition**, and negation.

Such a rule can be described structurally:

{
  "op": "all",
  "conditions": [
    { "op": "gte", "key": "evidence", "value": 3 },
    { "op": "eq", "key": "alarm_active", "value": false }
  ]
}

It looks like a small script, but it is fundamentally not one. You cannot call a function or change something randomly here. Romergo already knows all allowable operations, checks the structure, and executes it the same way in the Builder preview and in the published Player.

Effects: small commands without a programming language

A condition reads the state, and an effect changes it. Runtime Romergo supports several atomic operations:

In the regular choice inspector, the main scenario is intentionally simple: select a variable and record a new value through set. In advanced interface logic, conditions and effects can be set as checkable structures. For example, a transition between terminal states can simultaneously remember a decision, add evidence, and trigger an alarm:

![Scene editor Romergo: three choice options record lab, habitat, and server into the variable route](../assets/logic-without-scripts-choice-effects.webp)

*The effect is attached directly to the answer option: three choices record lab, habitat, and server into route. The variable field and the new value remain separate even in the narrow inspector.*

[
  { "op": "set", "key": "terminal_decision", "value": "cut-power" },
  { "op": "inc", "key": "evidence", "amount": 1 },
  { "op": "toggle", "key": "alarm_active" }
]

This approach has an important limitation: it is not a calculator for arbitrary formulas. You cannot write evidence * trust / 2, start a loop, or create your own function. Complex behavior is assembled from several explicit steps, and logic extension occurs through new checkable operations runtime, rather than by executing unknown code.

For the author, this is a little less limitless, but the story remains predictable. The same effects can be tested before publication, saved, synchronized between players, and reproduced after loading progress.

Why is there not yet another Flow inside the logical node

Despite all the predictability, the current approach has an honest drawback: for a person without development experience, the form "variable — operator — value" can already seem like programming. And when groups of conditions and several effects appear nearby, the declarative structure remains safe for Player, but does not necessarily become simple for the author.

It seems natural to eventually come to visual nodes and within the logic itself. Instead of a list of conditions, one could connect blocks **AND**, **OR**, **NOT**, variable comparison, and value changes. On paper, this looks friendlier than text or JSON.

But then I imagine a real scenario: the author opens a logic node on the chapter map, and inside it finds another Flow with its own nodes and connections. It turns out to be a graph within a graph. You need to understand where you are right now, how to return to the story level, and in which of the two Flow to look for the error. For a beginner, such nesting can be scarier than the current form.

Therefore, a compromise is used for now. The story route remains visual, while the conditions and effects inside the nodes are shown as compact explicit fields. It works, but I don't consider the interface finally solved. Perhaps later, a more understandable combination of ready-made templates, step-by-step setup, and visual blocks will appear—one that truly reduces complexity, rather than just shifting it to another editor.

Logic lives not only on the map

Interface scenes use the same state. A widget, message, answer option, task, notification, or media stream can appear only when showWhen matches. The metric value can be linked to a variable, and the transition between widget states can be triggered after a delay, based on a condition, or after a player action.

For example, the onboard computer screen can behave like this:

1. At first, only the locked terminal is visible.

2. After the player's choice, the effect sets terminal_unlocked = true.

3. The condition opens the ship schematic and new buttons.

4. Pressing the button changes the state of the widget and increases evidence.

5. When evidence >= 3, a hidden notification appears and a new quest branch becomes available.

The chapter map, normal choices, and interactive interface do not create three separate systems. They read and modify a single state object. Therefore, a clue found in dialogue can unlock an interface element, and an action within the interface can change a subsequent scene.

This is where declarative logic begins to feel like real programming. Only instead of files, functions, and the event bus, the author works with understandable entities of history: facts, choices, conditions, and transitions.

Why don't I intentionally add an arbitrary JavaScript

The ability to insert a script seems to be the shortest way to any new mechanic. But as soon as a published quest gets the right to execute arbitrary code, the entire product model changes.

It is necessary to decide which browser APIs are available to this code, how to restrict the network and storage, what to do with infinite loops, how to transfer the quest offline, how to synchronize it in multiplayer, and how to ensure that the old project continues to work after updating Player.

A declarative rule is much more boring than an arbitrary function — and that is precisely why it is more reliable. It can be validated before execution. One can understand which variables it reads and writes. Progress can be safely saved in the middle of a chain. The quest can be executed the same way in a browser, a standalone build, and the embedded Player.

A limited vocabulary here does not hinder logic, but sets its boundaries. If a truly useful operation appears, it is better to add it to the common runtime and give all authors the same verified behavior, rather than forcing each project to invent its own engine.

For dessert — your own HTML and CSS

At the same time, Romergo still leaves room for real code where it is safest: in designing a regular scene.

In the project, you can create your own appearance theme on HTML and CSS and use it in different chapters. HTML sets the layout of the prepared areas — dialogue window, portrait, character name, text, question, and choice buttons. CSS allows you to significantly change these areas: borders, background, card shapes, spacing, typography, and response to screen size.

Ready-made themes **Standard**, **Minimalism**, **Brutalism**, **Romance**, and **Neon** show the confirmed range without manual layout. When creating a custom theme, Romergo copies the selected template: after that, its HTML and CSS can be edited and immediately checked in the live Player preview.

![Actual formatting editor Romergo: CSS of the Brutalism theme and mobile dialog preview](../assets/logic-without-scripts-custom-html-css.webp)

*There is no concept art here: on the left is the real CSS of the built-in theme Brutalism, on the right is the result of the same code in the phone preview. The Desktop / Mobile switch allows you to immediately check responsive rules, and the custom theme starts working with a copy of the selected template.*

But the border remains the same. In the custom HTML there is no <script>, event handlers, forms, iframes, and external resources. CSS cannot load an external url() or @import. Required scene areas must remain in place; if the template lost them, Player reverts to a safe standard design.

That is, HTML and CSS are responsible for **how the story looks**, while nodes, variables, conditions, and effects are responsible for **how it works**.

I like this kind of separation. The author can make a quest visually different from the others, but its completion still remains part of the overall testable runtime. The outer layer can be radically recolored and rebuilt without turning each theme into a separate program.

Code without code does not mean absence of logic

Romergo does not aim to replace a programming language with pictures. A graph is poorly suited for complex mathematics, and a set of atomic effects will not become a universal automation engine.

However, for interactive stories, other things are often needed: remembering a fact, changing a counter, opening a choice, selecting a route, showing the interface state, saving a checkpoint, and conveying all these decisions to the next chapter.

For this, nodes and variables turn out not to be a simplified version of scripts, but a more direct language of the story itself. The author describes not commands to the computer, but the causal connections of the world: the player made a choice, the state changed, the story responded.

And if you want a bit of real code, HTML and CSS are still waiting for dessert.

Back to dev blog