Incompatible Changes
For complete changes, see the CHANGELOG.
0.29.1
Camera.resetCameraclears the filter on the first frame instead of easing it out across the duration. The pose still eases. A scene that timed a grade fading away with the pan loses the grade at once instead
0.29.0
- Dialogue text is typeset to the strict kinsoku rules in both writing modes, and a Latin word inside a CJK line is no longer split. Lines wrap in different places than before: a word that used to be broken now moves to the next line whole, so a dialogue box measured against the old wrapping can come out one line taller. No story needs editing for this
- The strict rules apply only when the document declares a language. A page hosting the player with no
langon it gets the browser's default rules, see Dialog
0.27.0
- A transition given to
Scene.jumpTois played across the whole stage rather than across the outgoing scene's background image. Sprites, text and every other layer now take part in the jump instead of vanishing at the end of it. Nothing about how a transition is written changes, but its geometry is now the stage: aPushtravels the width of the stage, aMaskis laid over the stage rectangle, and a background deliberately smaller than the stage is swept along rather than being the thing swept. See JumpConfig JumpConfig.transitionis widened fromImageTransitiontoTransition. Every built-in transition satisfies both, so existing calls are unaffectedallowSkipBackgroundTransitionis replaced byallowSkipSceneTransition, which defaults totrue. The old flag was never read: a background transition is skipped underallowSkipImageTransitionlike any other image. The new one governs the stage transition a jump plays. Rename it in anygame.configurecall, see GameConfig
0.26.0
Camera.resetis nowCamera.resetCamera. Every element carries an internalreset()— the lifecycle hook the engine runs when a new game starts or a save loads — and the camera was spending that name on the chainable authoring helper, sonewGame()never restored the camera's pose and a story that panned or zoomed kept that framing into the next playthrough. Rename anycamera.reset(...)in your script. Callingreset()on a camera now reaches the lifecycle hook: it restores the configured pose at once, animates nothing, and returns the camera rather than a chainable actionLiveGame.undono longer takes an action id, and returnsfalserather than throwing when there is nowhere to step back to. A line is named by its token through restoreToHistory.undo()with no argument keeps working and now works after loading a save as well, which it never did beforeLiveGame.getHistoryno longer includes lines ahead of the play head. During ordinary play nothing changes; after stepping back, the lines beyond the current one are returned by the new getFuture instead. A backlog built fromgetHistorytherefore stops listing what has not happened yetLiveGame.restoreToHistoryreaches in both directions and no longer discards the lines past the one it moves to. Code that relied on it truncating the backlog will now find those lines still reachable — the play head moves instead of the timeline being cut- Save format v3.
elementStateslists only the elements whose state differs from what the script wrote, and loading resets every element before applying the save. v1 and v2 saves load unchanged in 0.26.0, but an older engine cannot read a v3 save correctly: it skips the reset, so elements the save leaves out keep whatever the running session put in them. A host that writes element state outside the engine's action dispatch should callelement.markDirty(), or the save will not carry it — withapp.debug: truethe engine warns about state it finds unmarked. See SavedGame - A Character's name is now carried by saves.
setNamechanges used to be silently lost on load; a save written after a reveal came back as "???". Saves written by earlier versions carry no character entry and still load, leaving the authored name in place. Because characters now occupy save entries, an application that restores saves should name its cast throughDevTools.setElementStaticId - A Layer no longer carries its pose out of the scene that declared it. Leaving a scene now resets the layers that scene put on stage — configured z-index and pose — not only the displayables standing on them, and
newGame()does the same. A story that relied on a layer staying slid aside or faded out for the scenes that followed must now set it again. The story camera is exempt - A click on the stage advances ADV dialogue, where previously only the skip key did. A click completes the line being typed and advances a line that has finished; it never forces, so it does not run on through the scene the way a held skip key does. An application that had built its own click-to-advance on top of
event:state.player.stageClickwill now advance twice per click
0.23.0
- A Sound's
typeis now SoundBusId (SoundType | (string & {})) rather thanSoundType.SoundTypeis unchanged, still exported, and its three values still mean what they meant, so nothing that uses only those values needs to change — but aswitchover a sound'stypethat relied on it being an exhaustive union of three now needs a default arm Sound.voice(),Sound.bgm()andSound.sound()defaulttypeinstead of overwriting it.Sound.voice({src, type: "alice"})used to silently produce avoiceclip; it now produces one on thealicebus. Code that passed atypeto a factory and depended on it being ignored will now be honoured- A
Preferencecopies the defaults object it is handed rather than writing into it. TwoGames in one process no longer share one settings object, and moving a slider no longer rewritesGame.DefaultPreferencefor the rest of the process. A host that mutated the shared object on purpose must now go throughgame.preference
0.22.0
LiveGame.playSoundand a dialog line's voice start a clip at theSound's configured volume rather than at full volume.Sound.voice({src, volume: 0.4})played through either path used to sound at 1 and now sounds at 0.4. Callers that pass an explicit target are unaffectedSound.play()on abgm-typed clip no longer throwsStaticScriptWarning. It logs aconsole.warnand plays. A story that relied on the throw to fail loudly at chain-build time now runs
0.20.0
- The
Chained*type aliases —ChainedControl,ChainedPersistent, and their siblings — were removed. They were@internal, so they never appeared in the emitted declarations, and the public signatures that used them now name the chained type directly:Control.do()returnsProxied<Control, Chained<LogicAction.Actions>>,Persistent.set()returnsProxied<Persistent<T>, Chained<LogicAction.Actions>>. No signature changed shape and no runtime behaviour moved
0.19.1
StackFrameSnapshot.branchesis nowStackSnapshot[]rather than an array of frame arrays, so a branch arrives whole — with itsloopandtag— instead of being reduced to its frames.branches[i][0]becomesbranches[i].frames[0]. The type is experimental and read-only, which is why it was corrected rather than duplicated into a parallel field
0.17.1
- fastForward's
reasongained"stalled". Runtime behaviour for the existing values is unchanged, but the return type is wider: an exhaustiveswitchoverreasonneeds the extra arm to keep compiling. Hosts that ignore the result are unaffected - A fast-forward run can now end early on a step that cannot be skipped. Each suspended line is given
options.stepTimeoutms (default10000) to settle; a step that outlives it ends the run with"stalled"instead of continuing past it. Previously the run hung there and the promise settled neither way, so nothing that works today starts failing — but a host that treats any non-"menu"reason as success should now distinguish"stalled". RaisestepTimeoutfor a story that fast-forwards through long unskippable media
0.17.0
onPreloadComplete,oncePreloadComplete,whenPreloadComplete()andevent:preloaded.completenow fire before the game is entered — while a menu may still be on screen — rather than afternewGame()has mounted a scene. The entry scene is registered for preloading as soon as the story is loaded, which is the point of the release. Nothing fails to compile; the callback simply runs at a different moment- If you used it to gate a loading step, it now does its job better and needs no change
- If you used it to mean "the game has content on screen", switch to
onFirstSceneReady/whenFirstSceneReady(). Those are unchanged and still require a real mounted scene - See Preloading
0.16.0
The transition API was redesigned around two ideas: transition engines are instantiated with new and take a single options object, while the mask patterns they animate are built with the static factories on Mask and passed as the pattern option.
DissolveandFadeInno longer take positional argumentsnew Dissolve(1000, easing)→new Dissolve({duration: 1000, easing})new FadeIn(500, [x, y], easing)→new FadeIn({duration: 500, offset: [x, y], easing})— note the parameter rename fromstartPostooffset
SoftWipe,SoftIris, andBlindswere removed; use Reveal with the matching patternnew SoftWipe({duration, direction, feather})→new Reveal({duration, pattern: Mask.wipe({direction, feather})})new SoftIris({duration, center, feather})→new Reveal({duration, pattern: Mask.iris({center, feather})})new Blinds({duration, orientation, slats})→new Reveal({duration, pattern: Mask.blinds({orientation, slats})})
MaskTransitionwas removed; its hard-edged clips areRevealwith a zero-feather patternMaskTransition.circle({duration, center})→new Reveal({duration, pattern: Mask.iris({center, feather: 0})})MaskTransition.wipe({duration, direction})→new Reveal({duration, pattern: Mask.wipe({direction, feather: 0})})
ThroughColor's static factories were removed; it is now constructed withnewand takes its geometry as apatternThroughColor.fade({duration, color, hold})→new ThroughColor({duration, color, hold})ThroughColor.wipe({direction, feather, ...rest})→new ThroughColor({...rest, pattern: Mask.wipe({direction, feather})})ThroughColor.blinds({orientation, slats, ...rest})→new ThroughColor({...rest, pattern: Mask.blinds({orientation, slats})})ThroughColor.iris({center, feather, ...rest})→new ThroughColor({...rest, pattern: Mask.iris({center, feather}), inverted: true})— note theinverted: true: the classic iris-to-black closes rim-in, which is the pattern's inverted orientation
- Removed option types:
SoftWipeOptions,SoftIrisOptions,BlindsOptions,MaskTransitionCircleOptions,MaskTransitionWipeOptions,ThroughColorFadeOptions,ThroughColorWipeOptions,ThroughColorBlindsOptions,ThroughColorIrisOptions. New exported types:DissolveOptions,FadeInOptions,RevealOptions,ThroughColorOptions,ThroughColorUncover,MaskPattern, and the per-factory*PatternOptionstypes
0.13.0
SavedGame'sstoreis now typed as SerializedNamespaceData instead of StorableData. It always carried the tagged form; only the declaration was wrong. The save format itself did not change, so existing saves keep loading — but code that readsavedGame.game.storeby hand and relied on the old declaration will now see the tagged shape it was actually getting all along- Saves written by 0.12.x and earlier load correctly again, with one exception: a save produced by saving after loading on an affected version has its values tagged twice on disk, which cannot be told apart from a value the game legitimately stored. Such saves are not repaired
image.darken(darkness, duration)now animates overdurationinstead of applying instantly. Previously the animation only ran when an easing was passed as well, and the duration was silently dropped otherwise. Passdurationof0for the old jump-in-place behavior
0.9.0
JumpConfig.unloadScenewas removed
0.8.0
Displayable.scaleis changed. Now it takes two parametersscaleXandscaleYinstead of the previous singlescaleparameter
0.7.0
Routeris deprecated, useLayoutRouteras a more powerful routerPageis refactoredgame.config.skipKeyandgame.config.nextKeyare deprecated, usegame.keyMapinstead
0.6.0
- The game has completely transitioned from a single node currentAction to a StackModel. The model ensures that:
- Awaitable is handled in an explicit way
- Support for sub-stack model recursive calls
- Full support for serialization/deserialization
- Support for scenario operations to break the call stack
- Better branching/merging operations
- Less prone to state clutter when deserializing and undoing
- Is a complete solution for nested operations
game.config.skipIntervalis deprecated, useGamePreference.skipIntervalinstead
0.5.0
game.config.cpsis deprecated, useGamePreference.cpsinstead- Menu GameElementHistory.
selectedmay be null
0.4.0
game.config.elements.say.textIntervalis deprecated, usegame.config.elements.say.cpsinsteadgame.confighas been refactored, see GameConfig for more detailsgame.config.playeris deprecated, usegame.configinsteadgame.config.elementsis deprecated, usegame.configinsteadgame.config.elementStylesis deprecated, usegame.configinstead
0.3.0
- NarraLeaf-React now requires React 19 or later
- Image Config has changed:
- the type of
config.srcshould be a tag definition or a string - In tag-based image config,
config.srcas a resolver function is moved toconfig.src.resolve - Image can't be marked as wearable anymore, use
image.wearorimage.asWearableOfinstead
- the type of
- These methods of
Imagehas been changed:setAppearance,setTags,setSrc->charapplyTransform->transformwearis a new alias foraddWearableasWearableOfis a new alias forbindWearableinit,setPosition,dispose,copyare removedIImageTransitionis removed, useImageTransitioninsteadFadeis removed, use Dissolve instead, which takes the same duration and easing
- These methods of
Texthas been changed:applyTransform->transformapplyTransitionis removed, applying transitions are still in planningITextTransitionis removed, useTextTransitioninstead
- These methods of
Transformhas been changed:overwriteis removed- Transformer API is completely deprecated
- These methods/properties of
Scenehas been changed:activate,deactivateare removed, the game will manage the scene's lifecycle automaticallyapplyTransformis removed, usescene.background.transforminsteadinheritis removedrequestImagePreload->preloadImage
- These methods of
Soundhas been changed:- use
copyto create a new sound instance play,stopandsetVolumemethod can receive adurationparameterfadeis removed, usesetVolumeinstead
- use
- In displayable elements, the transform states are separated from the element states
- These changes are made to
Soundconfigsyncandtypeare removed- use
preloadto use Howler.js's preload feature - use
seekproperty to set the initial seek position
- Scene's config now can't specify the
invertYandinvertXproperties, use story configorigininstead Top,Center,Bottom,HBox, andVBoxare deprecated, usePageRouterAPI insteadITransitions are all deprecated, useTransitionAPI insteadFontSizeTransition->FontSizeBaseImageTransition->ImageTransitionBaseTextTransition->TextTransition
0.2.2
- SceneConfig
invertYis nowtrueby default
0.2.0
- Image constructor signature has changed. Now the first argument must be a config object.
0.1.2
game.config.player.widthandgame.config.player.heightcannot be string anymore