Skip to content

LSDE Dialog Engine / NativeProperties

Interface: NativeProperties ​

Defined in: types.ts:90

The block properties the ENGINE acts on, read out of block.props.

In v2 there is no separate bag: natives and the designer's own properties share props, keyed by bare id. Ids cannot collide — LSDE refuses a project property that takes a native name — so the only way to tell them apart is this list. NATIVE_PROPERTY_IDS is it.

Most of these are inert: the engine passes delay, timeout, debug, waitInput, portPerCharacter and skipIfMissingActor through untouched and lets the game decide. Two are not: isAsync opens a parallel track, and waitForBlocks holds a block until the ones it names have FINISHED.

Inert is not the same as free. A writer who fills a field in expects a behaviour, and the doc on each property below says which one. timeout is the one that is easy to implement backwards, so read it before wiring a timer.

delay and timeout are MILLISECONDS in v2. They were seconds in v1, and nothing will report the difference at runtime — a migrated project turns a 3-second pause into 3 ms.

Properties ​

debug? ​

optional debug?: boolean

Defined in: types.ts:125

Editor debug flag. Passed through.


delay? ​

optional delay?: number

Defined in: types.ts:94

Milliseconds to wait before the block runs. Applied by onBeforeBlock, not by the engine.


inPortPerCharacter? ​

optional inPortPerCharacter?: boolean

Defined in: types.ts:139

One ENTRY port per actor id, in as the fallback — the mirror of portPerCharacter.

The wire names the speaker: a link's toPort carries the CARD ID of the actor the block is to be assigned to on that pass. This is what lets several wires reach one block and each stand for a different actor — a block alone cannot tell which path brought it.

The engine still ASKS: onResolveCharacter is handed that one actor rather than the whole cast, and a game that returns undefined says the character does not exist. Entering through in names nobody, and the callback gets the whole list as everywhere else.


isAsync? ​

optional isAsync?: boolean

Defined in: types.ts:92

Run this block on a parallel track instead of the main flow.


portPerCase? ​

optional portPerCase?: boolean

Defined in: types.ts:143

Condition blocks: each case exits by its own port instead of sharing out.


portPerCharacter? ​

optional portPerCharacter?: boolean

Defined in: types.ts:127

One exit port per actor id, out as the fallback.


skipIfMissingActor? ​

optional skipIfMissingActor?: boolean

Defined in: types.ts:141

Skip the block when its actor is absent at runtime. Passed through.


timeout? ​

optional timeout?: number

Defined in: types.ts:117

MILLISECONDS the block STAYS once its line has been said — an auto-advance for blocks.

The countdown starts at the END of the reveal, not when the block is dispatched. What the writer sets is how long the line remains on screen after its last character has been typed (or its last syllable spoken), and then the block leaves on its own. Counting from arrival instead cuts the line in half whenever the text takes longer to reveal than the timeout allows — a 2500 ms timeout on a 120-character line truncates it mid-sentence.

It overrides NativeProperties.waitInput and it overrides leaving immediately. All three say WHEN the block is left, and the one the writer put on the card is the most specific answer. So a click no longer dismisses the block: it may only HURRY the reveal to its end, which is what arms the countdown. Pressing a line that plays its own time makes no sense; speeding it up does.

Leaving the block is also what marks it FINISHED, so on a block listed in a NativeProperties.waitForBlocks elsewhere, this is the property that releases the join.

The engine enforces none of it — no timers, no game loop, nothing is read here. The game arms the countdown, and this is the behaviour the writer is owed when they fill the field.


waitForBlocks? ​

optional waitForBlocks?: string[]

Defined in: types.ts:176

Block ids OF THIS SCENE that must have FINISHED before this block STARTS.

The join half of the fork NativeProperties.isAsync opens: a branch runs in parallel, and a block downstream waits for it to be over before it plays.

Finished, not reached. A listed block counts once the flow has LEFT it: the game called next(), the exit port was resolved, and the block's cleanup has run. So the bubble is off the screen and the audio voice is stopped before the joining line is dispatched — which is the whole point of drawing a join.

That is a change from the first v2 releases, where being reached was enough. It made the property nearly inert in the shape designers actually draw: a fork into two blocks, then a join on both, lifted in the very tick it was registered because the two had been dispatched a fraction of a millisecond earlier — and the joining line spoke over them.

The engine holds the block BEFORE dispatching it. No handler is called, so the game never learns the block exists until the wait lifts — nothing of it can reach the screen early. That is the engine's decision and not a rendering choice a game could make differently: this is a NATIVE property, the designer ticks it in LSDE, and the engine owes them the behaviour.

The rule is the same on every track, the one the player is watching included.

  • All the listed blocks must have finished, not just one.
  • Finishing a block releases everything waiting on it, in turn.
  • A block that never finishes parks its track for good — and a block that waits for input forever never finishes. init() reports UNKNOWN_WAIT_BLOCK when an id is not a block of the scene at all, but it cannot know whether a real one will ever be played.
  • getVisitedBlocks() is unaffected: it still lists what the player has been SHOWN, which includes a block still mid-sentence.

waitInput? ​

optional waitInput?: boolean

Defined in: types.ts:123

Wait for player input or a game signal instead of leaving on its own. Passed through, never interpreted — and outranked by NativeProperties.timeout, which says the block plays its own time and cannot be dismissed early.