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?
optionaldebug?:boolean
Defined in: types.ts:125
Editor debug flag. Passed through.
delay?
optionaldelay?:number
Defined in: types.ts:94
Milliseconds to wait before the block runs. Applied by onBeforeBlock, not by the engine.
inPortPerCharacter?
optionalinPortPerCharacter?: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?
optionalisAsync?:boolean
Defined in: types.ts:92
Run this block on a parallel track instead of the main flow.
portPerCase?
optionalportPerCase?:boolean
Defined in: types.ts:143
Condition blocks: each case exits by its own port instead of sharing out.
portPerCharacter?
optionalportPerCharacter?:boolean
Defined in: types.ts:127
One exit port per actor id, out as the fallback.
skipIfMissingActor?
optionalskipIfMissingActor?:boolean
Defined in: types.ts:141
Skip the block when its actor is absent at runtime. Passed through.
timeout?
optionaltimeout?: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?
optionalwaitForBlocks?: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()reportsUNKNOWN_WAIT_BLOCKwhen 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?
optionalwaitInput?: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.
