NarraLeaf

Incompatible Changes

For complete changes, see the CHANGELOG.

0.29.1

  • Camera.resetCamera clears 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 lang on it gets the browser's default rules, see Dialog

0.27.0

  • A transition given to Scene.jumpTo is 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: a Push travels the width of the stage, a Mask is 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.transition is widened from ImageTransition to Transition. Every built-in transition satisfies both, so existing calls are unaffected
  • allowSkipBackgroundTransition is replaced by allowSkipSceneTransition, which defaults to true. The old flag was never read: a background transition is skipped under allowSkipImageTransition like any other image. The new one governs the stage transition a jump plays. Rename it in any game.configure call, see GameConfig

0.26.0

  • Camera.reset is now Camera.resetCamera. Every element carries an internal reset() — 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, so newGame() never restored the camera's pose and a story that panned or zoomed kept that framing into the next playthrough. Rename any camera.reset(...) in your script. Calling reset() 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 action
  • LiveGame.undo no longer takes an action id, and returns false rather 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 before
  • LiveGame.getHistory no 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 from getHistory therefore stops listing what has not happened yet
  • LiveGame.restoreToHistory reaches 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. elementStates lists 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 call element.markDirty(), or the save will not carry it — with app.debug: true the engine warns about state it finds unmarked. See SavedGame
  • A Character's name is now carried by saves. setName changes 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 through DevTools.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.stageClick will now advance twice per click

0.23.0

  • A Sound's type is now SoundBusId (SoundType | (string & {})) rather than SoundType. SoundType is unchanged, still exported, and its three values still mean what they meant, so nothing that uses only those values needs to change — but a switch over a sound's type that relied on it being an exhaustive union of three now needs a default arm
  • Sound.voice(), Sound.bgm() and Sound.sound() default type instead of overwriting it. Sound.voice({src, type: "alice"}) used to silently produce a voice clip; it now produces one on the alice bus. Code that passed a type to a factory and depended on it being ignored will now be honoured
  • A Preference copies the defaults object it is handed rather than writing into it. Two Games in one process no longer share one settings object, and moving a slider no longer rewrites Game.DefaultPreference for the rest of the process. A host that mutated the shared object on purpose must now go through game.preference

0.22.0

  • LiveGame.playSound and a dialog line's voice start a clip at the Sound'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 unaffected
  • Sound.play() on a bgm-typed clip no longer throws StaticScriptWarning. It logs a console.warn and 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() returns Proxied<Control, Chained<LogicAction.Actions>>, Persistent.set() returns Proxied<Persistent<T>, Chained<LogicAction.Actions>>. No signature changed shape and no runtime behaviour moved

0.19.1

  • StackFrameSnapshot.branches is now StackSnapshot[] rather than an array of frame arrays, so a branch arrives whole — with its loop and tag — instead of being reduced to its frames. branches[i][0] becomes branches[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 reason gained "stalled". Runtime behaviour for the existing values is unchanged, but the return type is wider: an exhaustive switch over reason needs 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.stepTimeout ms (default 10000) 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". Raise stepTimeout for a story that fast-forwards through long unskippable media

0.17.0

  • onPreloadComplete, oncePreloadComplete, whenPreloadComplete() and event:preloaded.complete now fire before the game is entered — while a menu may still be on screen — rather than after newGame() 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.

  • Dissolve and FadeIn no longer take positional arguments
    • new 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 from startPos to offset
  • SoftWipe, SoftIris, and Blinds were removed; use Reveal with the matching pattern
    • new 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})})
  • MaskTransition was removed; its hard-edged clips are Reveal with a zero-feather pattern
    • MaskTransition.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 with new and takes its geometry as a pattern
    • ThroughColor.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 the inverted: 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 *PatternOptions types

0.13.0

  • SavedGame's store is 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 read savedGame.game.store by 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 over duration instead of applying instantly. Previously the animation only ran when an easing was passed as well, and the duration was silently dropped otherwise. Pass duration of 0 for the old jump-in-place behavior

0.9.0

  • JumpConfig.unloadScene was removed

0.8.0

  • Displayable.scale is changed. Now it takes two parameters scaleX and scaleY instead of the previous single scale parameter

0.7.0

  • Router is deprecated, use LayoutRouter as a more powerful router
  • Page is refactored
  • game.config.skipKey and game.config.nextKey are deprecated, use game.keyMap instead

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.skipInterval is deprecated, use GamePreference.skipInterval instead

0.5.0

  • game.config.cps is deprecated, use GamePreference.cps instead
  • Menu GameElementHistory.selected may be null

0.4.0

  • game.config.elements.say.textInterval is deprecated, use game.config.elements.say.cps instead
  • game.config has been refactored, see GameConfig for more details
    • game.config.player is deprecated, use game.config instead
    • game.config.elements is deprecated, use game.config instead
    • game.config.elementStyles is deprecated, use game.config instead

0.3.0

  • NarraLeaf-React now requires React 19 or later
  • Image Config has changed:
    • the type of config.src should be a tag definition or a string
    • In tag-based image config, config.src as a resolver function is moved to config.src.resolve
    • Image can't be marked as wearable anymore, use image.wear or image.asWearableOf instead
  • These methods of Image has been changed:
    • setAppearance, setTags, setSrc -> char
    • applyTransform -> transform
    • wear is a new alias for addWearable
    • asWearableOf is a new alias for bindWearable
    • init, setPosition, dispose, copy are removed
    • IImageTransition is removed, use ImageTransition instead
    • Fade is removed, use Dissolve instead, which takes the same duration and easing
  • These methods of Text has been changed:
    • applyTransform -> transform
    • applyTransition is removed, applying transitions are still in planning
    • ITextTransition is removed, use TextTransition instead
  • These methods of Transform has been changed:
    • overwrite is removed
    • Transformer API is completely deprecated
  • These methods/properties of Scene has been changed:
    • activate, deactivate are removed, the game will manage the scene's lifecycle automatically
    • applyTransform is removed, use scene.background.transform instead
    • inherit is removed
    • requestImagePreload -> preloadImage
  • These methods of Sound has been changed:
    • use copy to create a new sound instance
    • play, stop and setVolume method can receive a duration parameter
    • fade is removed, use setVolume instead
  • In displayable elements, the transform states are separated from the element states
  • These changes are made to Sound config
    • sync and type are removed
    • use preload to use Howler.js's preload feature
    • use seek property to set the initial seek position
  • Scene's config now can't specify the invertY and invertX properties, use story config origin instead
  • Top, Center, Bottom, HBox, and VBox are deprecated, use PageRouter API instead
  • ITransitions are all deprecated, use Transition API instead
    • FontSizeTransition -> FontSize
    • BaseImageTransition -> ImageTransition
    • BaseTextTransition -> TextTransition

0.2.2

  • SceneConfig invertY is now true by default

0.2.0

  • Image constructor signature has changed. Now the first argument must be a config object.

0.1.2

  • game.config.player.width and game.config.player.height cannot be string anymore

On this page