Changelog
v0.15.0
New Features
Achievements
- Achievements. A new Achievements editor section – Achievements, Tiers and Groups – defines the entries, their prestige rank and the sub-tabs the in-game tab is split into. The engine stores progress, scores the points, pops the notification and draws the tab, and registers nothing when a game defines no achievements.
- Achievement progress API (
game.progressAccolade). Scripts do the tracking:progressAccolade(id, delta),setAccoladeProgress(id, value),progressAccoladesByTag(tag)andsetAccoladeTarget(id, n). Progress only ever rises, and the newaccolade_completedemitter hands you the id and its points to pay out.
Content and DryadScript
choose_itemaction. Opens an item picker from content, filtered byid,categoryortags, and stores the pick inactive_itemand the newchosen_item_idstate. Branch on it with the new_chosen_item(id)condition.- Book reader (
booktrait). An item whosebooktrait names a scene becomes a paged reader – one paragraph per page, with a Read choice added to its card. Bookmarks persist per item inbook_bookmarks, and{read_book: true}opens one from content. - Paintings (
view_painting). An item with thepaintingtrait can be looked at: the named asset goes full-screen with the item's description as the text. Leave the trait empty and the item id doubles as the asset id. - Key locks (
key/key_consume). Rooms take these two fields and inventories take them as traits, so entering a locked room or opening a locked chest spends a key from the party bag. Scene-driven movement bypasses the lock, and the newkey_usedemitter reports what opened what. - Gather spots (
collect_pool). A collectable can draw its item from a table instead of naming one, resolved through the newcollectable_resolveemitter at dungeon creation. Prose on the@line can use the new|title|and|description|placeholders for the item that was drawn. - Scene actor rail (
panel_actor). Scenes list their cast in a rail opposite the party list, and clicking a face opens that character's viewer.{panel_actor: "alice, bob"}adds someone with no art on stage,hide_actor_listsuppresses the panel for a beat. _abilitycondition. Whether a character currently has an ability:_ability(alice.fireball) = 1. It reads the final set, so abilities from a status or an equipped item count.
Game API and emitters
- Item discovery.
game.discoverItem(id),game.isItemDiscovered(id)andgame.getDiscoveredItems(), backed by anitem_discoveredemitter that fires once per item per save. Learning a recipe, finishing a book and viewing a painting each discover the item automatically. - Shadow dungeons.
game.addShadowDungeon(id, { config, content, rooms, encounters })defines a dungeon at runtime that the engine treats like an authored one, from raw DryadScript or a pre-parsed line array. Ids must start with an underscore, definitions persist in saves, andgame.removeShadowDungeon(id)cleans up. game.reorderParty(orderedIds). Reorders the party with no join or leave side effects, rejecting anything but the exact current members in a new sequence. Party order drives the character rail and the default battle roster, and players can now drag the rail themselves.window.engine.popups.closeAll()drops the transient hover stack andclosePopupsByKey(prefix)removes pinned cards too. Reach for it before opening a dialog of your own, since these cards paint above the modal layer.status_apply_beforeemitter. Fires before any status lands, reapplies included – whichstatus_addedskips. Returnfalseto refuse it, or mutatestacks,durationorsourcein place to change what lands.- Inventory and economy emitters.
inventory_transfer_afteris the post-mutation counterpart ofinventory_transfer, andinventory_craftfires after a recipe is crafted.currency_changefires once per currency whenever a payment lands, negative when paid and positive when received. quest_updatedemitter. Fires after a quest log line lands, with(questId, goalId, result, dungeonId). The result tells a brand new quest from a goal that just closed one out, so you needn't track quest state yourself.item_drop_renderemitter anditem.isDroppable(). Both discard affordances now ask the same two questions:isDroppable()is the engine's rule (equipped gear, quest rarity), the emitter is the game's own veto. It is a pure predicate that runs on every render, so listeners must only return.
Components and slots
SavelistandStatusCardexported components.Savelistis the engine's own save/load list, for a game-over screen that has to offer a way back.StatusCardis the full status cardStatusBrickshows on hover, exposed to mount as your own popover.- Built-in Quest filter tab. The inventory filter bar ends with an engine-owned tab that collects every item of quest rarity, whatever category each one sits in. Games no longer need a Quest category of their own, and re-skin the tab through the
.inventory-filter-icon-questCSS class. item-card-topcomponent slot. Renders at the top of the item card body, above the description, for a readout that belongs beside the item's name. The existingitem-cardslot is unchanged and still renders at the bottom.
Authoring
- Ability effect
orderanddescription_attach.orderpins both the card's display order and the resolution order, lowest first, so a merged ability no longer inherits the order its modifiers were collected in.description_attachis free text under the effect name for behavior no aspect can express, and it runs through the text resolver. - Skill tree
order. Sorts the tree tabs, lowest first, and decides which tree opens selected. Trees left on0keep the sequence the character was granted them in. - Dismiss and rename (
dismissable,renameable). Two Essentials character traits that add a confirming Dismiss button and the inline rename control to the character sheet. Both hide under the newviewerModeprop, so a battle inspect popup can never delete a live combatant. - Recipe groups. A new Recipe Groups editor tab, a
recipe_groupfield on each recipe and agroup_recipeslist on each inventory. Membership lives on the recipe, so a new recipe joins every station that lists its group by setting one field. - Developer-only ability fields (
debug_info). Flag an ability definition and its raw value prints at the bottom of the card – but only in dev mode with Show Hidden Stats on. Works onmetaandaspectroles, for tuning fields like AI weights that have no player-facing meaning. - Entity id badges on cards. Ability, item, status, stat, skill, tree and record cards now show the entity's id in a small monospace badge. Always on in the Engine Editor, and in-game behind the Show Hidden Stats dev setting.
- Spine asset transitions and idles. Spine scene assets now take the same enter, exit and idle animation fields as images and videos. The enter transition waits for the skeleton to arrive, so it reveals the art instead of fading in an empty box.
- Borrowed status icons. A status with no art of its own wears the icon of whatever applied it – the potion, the equipped item, or the ability that cast it. The source is resolved live through the new
Status.displayImage, so re-icon the item and every existing save follows.
Engine Editor
- Abilities picker. Character templates, statuses, item templates and skill slots gain an Abilities button that opens a visual picker where every candidate renders as the real in-game card. Filters come from the ability schema, and each card can copy its id or save and jump to it in Ability Templates.
RPG Battler plugin
- Multi-wave battles (
enemies2). A second enemy list spawns instead of victory once the first is wiped, and the party keeps its health, statuses and cooldowns. Scripts can passstart({ waves: [[...], [...]] }), and the newbattle_wave_startemitter carries the wave index and incoming ids. - Support combatants (
battle_support). A party member who joins every battle outside the party limit and acts from the sidelines as a floating portrait. They can never be targeted or damaged, so they drop out of targeting, splash, ally counts and the defeat check alike. - AI-driven party members (
battle_ai). Hands a player-side member to the battle AI, which picks from that character's own kit. The trait is read fresh each turn, so a status carrying it takes control mid-fight and gives it back when it expires. - Battle roster traits (
battle_ignore,battle_always).battle_ignorekeeps a member off the battlefield entirely, for shopkeepers and pets who travel with you but don't fight.battle_alwayslocks one into the picker, preselected and not toggleable. - Pre-battle party picker. A roster larger than the battle limit now opens a picker instead of silently taking the first N.
start()returns{ ok: false, reason: 'party_select_pending' }while it is up, and the Battle Config fieldmax_party_sizeis nowmax_battle_units. - Unit caps (
max_total_units,max_enemy_units). Two summon budgets in Battle Config: the player cap counts the whole battle including the dead, the enemy cap counts only the living, so summoners refill but never overflow. The newsummonFromTemplate(templateId, side)service checks the cap before creating the character, andatUnitCap(side)exposes the same check. - Turn-order grace window (
round_start_delay). Every round opens with a "Turn N starts" banner held for a configurable beat, default 1500 ms, with the camera pulled out. Initiative is already settled behind it, so the lineup is readable before anyone moves. - Multi-hit strikes (
flurry).flurry: 3hits the same target three times, re-resolving the effects only – no cost, cooldown or charge is re-paid, and armor is paid per strike. Reach forbounceinstead when you want hops between random targets. free_actionaspect. The cast costs no action, so the caster acts again instead of ending their turn. It is per effect rather than in meta, so pairing it withchancegives a chance to act again.- Cast-baked stat bonuses (
crit_chance,crit_multi,accuracy). Three effect aspects that add to the caster's own stats for the single cast carrying them. They fill a gap a self-applied status cannot, since a status lands after the damage loop and can only help the next cast. lifestealcharacter stat. Lifesteal is now a stat every ability reads, so gear and passives grant sustain across a whole kit, with the effect aspect adding on top. Aspects sum across effects instead of the highest one winning.reflectstatus and stat. A multi-stack positive status returning 1% per stack of the damage that reaches health, unmitigated and without consuming stacks. It is the mirror of thorns: reflect answers the wound, so a shield stops it, while thorns answers the blow and now fires through a full absorb.- Status splash (
splash_statuses). Spills a percentage of the target statuses to the same neighborssplash_countreaches. It is independent of the damage share, so a pure crowd-control ability can splash with no damage at all. summon_amountaspect. Spawns several copies of one template from a single summon effect, so the card reads "Summon 3 Slime". Each copy is checked against the unit cap before it is created, so an overflowing clutch spawns what fits and stops.- Passive obstacles (
meta.prevents_action). A status whose holder forfeits every battle action while it is held, for obstacles and training dummies. They still take their turn slot, so DoT ticks, durations and turn emitters all fire. battle_finishedemitter. Fires the moment a battle is decided, every time including a re-fight, with(result, battleId). The Experience plugin's loot and XP now run from it, so a re-fought battle pays out where it used to pay nothing.- Battle service helpers. Four additions to
rpg_battle:heal(targetId, amount, opts)pushes a raw number through the full heal pipeline,getSide(charId)answers player or enemy, andgetNeighbors(targetId, count, includeSelf)returns the living combatants beside a target.getRoster(battleId)expands a battle into one entry per body across every wave.
Experience plugin
- Smith station (
smithaction).{smith: true}opens a forge where level-stamped equipment is reforged upward or broken down for stones. Newsmith_upgrade/smith_breakemitters veto either operation, and asmithservice exposessmithableItems(),breakYield()andupgradeCost(). - Environments (
environment/environments). A new Environments config tab defines the game's own vocabulary, read by theenvironmentdungeon trait and theenvironmentsitem trait. The plugin'sgatherpool then draws only ingredients legal for the place. - Enemy dungeon-scale curve (
enemy_scaling). Experience Config takes{level, coef}pins, read back throughgame.getService('reward').enemyScale(level). The plugin only publishes the curve – the game applies it where enemies are built, typically a stat computer. - Drop simulators (
simulateBattleLoot,simulatePool). Tworewardservice calls that dry-roll loot into a throwaway inventory and grant nothing, twenty rolls by default. They run the same code a victory and a chest use, so what they report cannot drift from what the game drops. - Leave loot behind. Every party-loot brick on the reward panel gains a trash toggle, which marks the line and removes it again on continue. It respects
item.isDroppable()and the game'sitem_drop_renderveto, so protected kinds stay put. reward-panel-bottomslot. The reward panel ends with a component slot, so a game can hang its own block below the rewards. Context arrives as props, so the component declaresprops: ['result'].
Turn System plugin
- Stack bleeding (
stack_bleeding). A status meta key that strips N stacks per adventure turn, oldest instance first, removing the status at 0. Reach for it where the stack count itself is the timer, so power and remaining time are one number. - A lot of bug fixes and improvements
v0.14.2
New Features
game.hasState(key). Whether a state key has been registered.getStatethrows on an unregistered key, so a base-game code path that reads a state a MOD registers had no safe way to ask. Now it does:game.hasState('mod_flag') && game.getState('mod_flag').replay_mode_unlock_scenesstate. Opens every gallery scene for replay, including ones the player never reached, and reveals their names. Defaults tofalse. Its existing companionreplay_mode_unlock_choicesungates the choices inside a scene that is already unlocked.game.closeMenu(). The counterpartopenMenu()never had. Needed before opening a popup from amenu-before/menu-afterslot: the menu paints above the popup layer, so a popup opened from inside the menu stays invisible until the menu closes.
v0.14.1
New Features
reset_characteraction. Rebuild a character from its template under the same id — a narratively fresh entity in the same role. Party membership is kept; statuses, resources, the private inventory and learned skills are discarded. When nothing with that id is live it creates the character instead, so one call guarantees a pristine character without the content having to know whether one exists yet.
v0.14.0
New Features
- Inline choices (
>). Attach a choice menu directly to a paragraph with>Label{...}lines — the buttons replace the normal "click to continue". - Hidden encounters (
discover).@x{discover: "perception#6"}keeps an encounter invisible until any party member meets the stat threshold, then reveals it permanently - Action gates. Registered actions can now define a
gate(value, ctx)that runs before a paragraph renders and before any of its actions fire; returning a scene id aborts the paragraph — its prose is skipped, its other actions never run, and the reader jumps elsewhere. - Choice hints (
clue). Mark a choice{clue: true}and it renders highlighted until the player takes it. In map and screen dungeons the encounter holding an untaken clue glows on the map too, so players can see there's something here they haven't done. - Scene colour grade (
grade).{grade: "night"}darkens and cools the world art — background assets, character art and the exploration map — crossfading over about a second, so daylight art can carry a night scene with no second painting. It follows the art wherever it is drawn, so a battle fought at night grades its backdrop and its fighters too. UI never grades: dialogue, choices, toolbar, and in battle the health bars, ability panel, turn order, floating damage and log all stay at full brightness. Presets cover time of day, weather and place, elemental and magical light, states of mind, and utility looks like sepia and greyscale — plus a#0.5strength suffix and an object form for manual brightness/saturation/tint. Persists across rooms and saves until you clear it with{grade: false}. - Collectables. A new
collectableencounter type: pick an item, place it on the map, and the player can collect it while on the map. encounter_selectedemitter. Fires when the player selects an encounter on the map or screen, so a script can act on the click itself with no choice to press. Returnfalseto block the selection.- Dungeon name in the toolbar. The map toolbar now shows the current dungeon's name, and a spinner in its place while the dungeon's art loads.
- Dungeon art preloads. Entering a dungeon fetches its background, fog mask and encounter images before the map is drawn, so it appears in one piece instead of popping in image by image. Each dungeon is warmed once.
- Wait a turn (Turn System plugin). A Wait button in the map toolbar passes one turn on the spot: statuses tick, collectables regrow, and hidden encounters are re-scanned. Turn Config can show the turn counter beside it.
- Skin-layer masks: targeting and clip-to-shape. A static layer's mask can now name which layers it applies to (
mask_targets, empty = all below) and choosemask_mode:hidecuts a hole (existing behavior),keepclips the targeted layers TO the polygon — e.g. hair trimmed to fit under a hat. - Status-gated abilities (
meta.require_status). An ability exists for a character only while they hold any of the listed statuses — hidden from the panel, sheet, battle and AI otherwise. Lives in meta, so modifiers (e.g. equipped items) can inject the gate at runtime. - Spine asset slot tints and removals. Spine scene assets take
slot_colors(RGB multipliers × brightness per slot — values above 1 re-brighten dark base art, e.g. recoloring red-based hair) andslot_remove(hide a slot's attachment). Re-applied automatically after skin changes; scripts can inject both at play time through theasset_renderemitter. transfer_itemaction. Move items between inventories from content:{transfer_item: "gold#200 -> merchant"}. Source defaults to the party inventory and the transfer aborts cleanly when the source lacks the quantity — pair with{active: "_item_count(gold) >= 200"}for pay-to-play choices._item_countcondition. How many of an item an inventory holds (party by default):_item_count(pickaxe) > 0,_item_count(chest.gold) >= 100._char(x.status.y)condition. The_charcondition can now check whether a character holds a status:_char(mc.status.blessed) = 1.- Skin-layer visibility via
attr. Simple show/hide layers no longer need a dedicated on/off attribute: when the key of anattraction is a skin layer id instead of an attribute,true/falseshows or hides the layer —{attr: "mc.wings = true"}. Works in conditions too:_char(mc.attribute.wings) = true. On an id collision the attribute wins (a warning is logged at load). - Attribute
order. Character attributes take anorderfield that sorts the attributes list and controls the segment order of multi-attribute skin-layer image keys (applied when the layer is saved in the editor). game.setFlag(id, value). The flag setter is now on the public API next togetFlag(supportsdungeon.flagscoping).- Battle opening statuses (RPG Battler plugin). The battle action takes a
statuseslist applied to every party member as the fight opens —{battle: {battleId: "bats", statuses: ["advantage"]}}for ambushes and similar setups. item_viewaction. Show an item's card under the dialogue text:{item_view: true}for the active item (the one whose custom choice opened the scene),{item_view: "item_id"}for a party-inventory item; cleared when the event ends. Pairs with the bare|item|placeholder and no-item-id_item_on(char)condition, which now both resolve the active item.maxkeyword for resources. Resource ops takemaxin place of a number, resolved per character to that resource's cap:{resource: "ane.health = max, ordelia.health = max"}is a full heal for the party. Works with every operator and oncan_overflowresources, where a large literal would keep going instead of stopping at the cap.
Improvements
- State markers
+text+/++text++. Mark prose as the initial or altered state of a changeable thing — rendered as.initial(purple) and.altered(orange) spans, restylable from game CSS. Stray pluses like+43 healthstay literal, and both markers highlight in the content editor. - Choice labels are now resolved.
|placeholders|,**bold**and*italic*in a choice's label now render, the same as body prose. - Map encounters fade in and out when they show or hide, using each encounter's
fadeTime/fadeOutTime. - Save restoration is declared, not called.
game.registerSaveMigration('_core', {...})at script-load time replaces calling the migration from agame_initiatedlistener — the engine merges every declaration and runs one pass itself on save load, before your listeners see the data. Mods and plugins register under their own id and the merge is restrictive, so a mod can widen coverage for its own content but afalseor askipfrom any source always wins. It covers every section it can touch — stats, traits, attributes, abilities, skin layers, spine views, static art placement, item slots, skill trees, learned skills, statuses and the four item sections — each takingtrue,false,{ only: [ids] }or{ skip: [ids] }. Sections nobody mentions followmode:opt-out(the default) syncs them,opt-inskips them, so you can re-push one thing (say a recalibrated face crop, spine and static art) without disturbing the rest of the save. Item slots are repositioned as well as backfilled, so moving a slot on the doll in the editor now reaches old saves too. - A lot of bug fixes and inner improvements
v0.13.0
New Features
- Item slot
accepts— general vs character-specific equipment. A slot type can now list extra slot tokens it also admits, on top of its own id. - Item Categories — player inventory filtering. New Items > Item Categories registry (name + icon image + order) and a
categoryfield on item templates. The party inventory now shows a filter bar: a built-in All tab plus one tab per category (its icon, or the name as a chip when no icon is set) and a name search box. - Art Manager on items, statuses & skill slots. The visual Art Manager (spine/static art positioning,
art_dx/art_dy/scale, face crop) is now available on Item Templates, Character Statuses, and Skill Slots — not just character templates. - Actor animation
inherit. Scene actor slots now acceptinheriton theirenter,exit, andidlefields (the new default). When an actor moves to another slot,inheritkeeps its current animation instead of resetting. equipscene action. Equip and unequip items by live character and item ids:equip: "riko -> armor1 & ring1",riko -> !armor1to unequip,riko.slot_type -> ring1to target a slot type,!riko.slot_typeto clear a slot, and!rikoto clear all. Missing items/slots log a warning instead of breaking the scene. Prefer it over the uid-basedequip_item/unequip_item.
Improvements
- A lot of bug fixes and inner improvements
v0.12.0
Breaking Changes
- **Content action
{discover_lore: ...}renamed to{lore: ...}.
New Features
game.isOldSave()– Returnstruewhen the loaded save was made on a previous(game + mods)version,falsefor new games or version-matched saves. Useful for retroactive migration hooks ingame_initiatedlisteners (e.g. adding new slots / states / data to existing saves after a version bump).[[item:id]]and[[status:id]]lore-link syntax. Narrative text now supports kind-prefixed lore links —[[item:lust_shift]]renders an item card on hover;[[status:fox_1]]renders a status card. Bare[[id]]continues to resolve to records (backwards-compatible). Custom labels ([[item:lust_shift>that pink shift]]) and therecord:prefix work too. The!discovery marker only applies to records.- Record
parent_recordfield — layered, discovery-gated encyclopedia entries. A record can declareparent_record: "some_id"to attach itself as an addendum to that parent.
Improvements
item_slotcontent action —[x, y]is now optional. Coordinates default to0, 0when omitted. Lets text-based games (no character doll) add slots without dummy coordinates:{item_slot: "mc->outfit & collar & panties"}. The bracketed formcharId.slotId[x, y]continues to work for games that position slots on a doll.- A lot of bug fixes and inner improvements
v0.11.0
New Features
- RPG Battler – A lot of new features, better camera movement, difficulty settings.
Improvements
- Game Settings – readable via
getData–game.getData('game_settings')now returns the merged setting definitions keyed by id, so scripts can read a setting'svalues/options (e.g. to build a custom settings UI) the same way other data files are read. - A lot of bug fixes
Breaking Changes
- Unified
partyaction –join_partyandleave_partyare merged into a singlepartyaction. A bare character id joins, a!-prefixed id leaves:{party: "alice, !bob"}. Update any scenes still using the old action names.
v0.10.0
Breaking Changes
- Text emphasis now follows standard markdown –
*text*renders italic and**text**renders bold.
New Features
- Multi-stack statuses – New
multi_stackboolean oncharacter_statuses. Whentrue, each apply creates an independent instance with its own duration (DoT/poison-style). Whenfalse(default), reapply refreshes the single instance. - Status metadata bag – New
metafield oncharacter_statuses, populated from a new Characters → Status Meta editor tab. Plugins (and games) define their own metadata keys here (e.g.power_scaling,dot_damage_type,is_battle) - Per-instance source tracking – Statuses store an optional
sourcefield on each instance, captured fromcharacter.addStatus(status, { source: casterId }). Useful for reflecting damage back to the caster, attributing log entries, etc. - Status lifecycle emitters –
status_added,status_removed,status_expiredare now part ofCORE_EMITTER_SIGNATURES. Listen for any status transition without polling. save_load_beforeemitter – Fires with the raw save JSON immediately before deserialization. Listeners may mutatesaveDatain place to migrate old-shape data, or returnfalseto abort the load. General-purpose primitive for game-side schema migrations.- Unified popover system –
v-popovernow drives every floating UI surface (status / item / stat / skill bricks, ability tooltips, record links,[v:status]references). - Stat breakdown popup – Hovering a stat now lists every status contributing to its value, grouped by the source(Base, equipped items, learned skills, other statuses).
v0.9.0
New Features
- Save restoration – When
manifest.version(or a mod's version) changes between saves, the engine can rebuild every character and every item from the current data definitions. See the new Advanced → Save Restoration doc.
Editor Improvements
- Better Spine DX – Character/asset popups now show a Spine stats panel listing every animation and skin from the loaded skeleton.
Breaking Changes
- Unified character canvas – Static and Spine characters now render into the same canvas shape.
- Action consolidation –
add_status,add_skin_layer/remove_skin_layer, andadd_item_slot/remove_item_slotare gone. Replaced by three new content actions with a sharedtargetId->item & item, targetId->!itemsyntax (!prefix = remove):{status: "alice->buff1 & buff2, bob->!debuff"}{skin_layer: "alice->armor & helmet, bob->!cloak"}{item_slot: "alice->ring, bob->!belt"}
v0.8.0
New Features
- New main screen – Cinematic per-game landing page with full-bleed background asset, glassy header, accent theming, ambient soundtrack, and a Continue/New Game buttons.
- Visual Dungeon Content Editor – New popup editor on Dungeon → Config for authoring DryadScript without touching raw text or relying on Google Docs.
- Lore & Encyclopedia – Author lore/tutorial records once, reference them inline as hoverable
[[link]]tooltips (BG3-style stacking on nested links) and as a browseable Encyclopedia tab. Supports discover-on-encounter syntax ([[!id]]), custom labels ([[id>label]]), the{discover_lore}action, and progressive reveal viaauto_discovery. - Multiple App instances – Saves now work correctly across multiple opened instances of the app.
- HTML editor asset picker – The Insert Image popup now lets you search and pick any project image (with thumbnails) alongside the existing URL paste, and a new Insert Video button does the same for
.mp4/.webm/other video assets.
v0.7.0
New Features
- Mobile compatibility — Phones and tablets are now supported in landscape. Portrait orientation shows a full-screen rotate-device prompt.
- Text dungeon overhaul — New text-dungeon layouts with a side column for custom components and a refreshed scene flow.
- a lot of bug fixes and quality of life improvements
v0.6.0
New Features
staticFaceForceprop on CharacterFace — Forces spine characters to useface_staticimage instead of live spine rendering. Use in logs, sidebar, and turn order to save performance.face_static_precedencetrait — Per-character opt-in to preferface_staticover spine crop in CharacterFace.- Spine viewport controls — Background spine assets support
dx,dy, andzoomviewport adjustments in the editor. is_hiddenstatus property — Statuses can be marked hidden from the UI via theis_hiddenfield.- Context-aware string resolution —
game.resolveString()accepts an optional context parameter. New|stat(statId)|placeholder reads character stats from context. game.getResolveContext()— Public API for accessing resolve context in custom placeholders.- Create Game from Template — New wizard in the editor (New → New Game) lets you create a game from a pre-made template in seconds. Pick a template, enter a name, and you have a working game ready to edit.
- Ability Groups — New
ability_groupseditor tab under Characters. Assign abilities to groups via thegroupmeta field. The RPG Battler ability panel and character sheet ability viewer render tabs per group. If no groups are defined, abilities display as a flat list. getGroupedAbilities()— New Character API method that returns abilities organized by their assigned groups.- Experience Plugin — New engine plugin for XP and leveling. Characters with a
leveltrait automatically gain a managedxpresource. Configurable progression formulas (linear/exponential percentage scaling), multi-level overflow,character_level_upemitter,_level(characterId)condition,{xp: amount}action, and XP bar UI in the character sheet. - RPG Battler Plugin — Turn-based RPG battle system with player-controlled party combat, ability groups, splash targeting, status durations, shared cooldowns, dynamic turn order, combat stats display, battle log service, defeated battle tracking, and more. See the plugin docs for full details.
Bug Fixes
- Fixed a bug where items could not be consumed.
- Fixed broken character equipped item slots position
- Fixed ability card effects without
idbeing treated as new (golden outline) instead of core (yellow outline).
v0.5.0
Version 0.5.0
Improvements
Empty Paragraph Auto-Skip
- Paragraphs that resolve to empty text after inline
if{}/fi{}processing are now automatically skipped. The engine advances to the next paragraph without displaying a blank dialogue box. - This allows conditional paragraphs that produce no output (e.g.,
if{flag = 0}Optional text.fi{}) to be silently bypassed when the condition fails, without needing manual{redirect}workarounds. - Only triggers when both the output text is empty and no actions are attached to the paragraph.
Intro Scenes ({intro: true})
- Scene paragraphs can now use
{intro: true}in params to play block 1 (column 1) only on the first visit. On subsequent visits, the engine automatically redirects to block 2 (column 2). - Useful for first-time introductions, name reveals, or one-time exposition that should be replaced by a shorter greeting on repeat visits.
Spine Character Dolls
- Characters can now use Spine skeletal animations as an alternative to static layered images. Configure atlas, skeleton, and default animation in the character template's Spine section.
CharacterDollcomponent auto-detects spine config and renders the Spine animation – no component changes needed for game scripts.- Convention-based skin mapping – character attribute values are used directly as Spine skin names. Changing an attribute reactively updates the Spine skins.
- New
animationtype for thecharaction:{char: "mc.animation=idle"}switches the playing animation from content without code. - Script API:
character.setSpineAnimation(name),character.isSpineCharacter(),character.getSpineSkins().
Character Views
- New Character Views system for rendering characters from different perspectives (back, side, etc.). Define views in Characters > Views, then tag skin layers or add
spine_viewsentries on templates/statuses. - View-tagged layers are excluded from default rendering (scenes, portraits, galleries) and only appear when explicitly requested via the
viewsprop onCharacterDollorCharacterSlot. - Static and Spine rendering are independent per view -- a static character can have a Spine back-view, or vice versa.
- Script API:
character.isSpineForView(view),character.getSpineForView(view),character.getImageLayersForViews(views).
Bug Fixes
- Fixed a bug where items could not be equipped.
{actor: false}now properly triggers exit animations before removing characters from the scene. Previously it cleared all actors instantly without playing exit transitions.
v0.4.0
Version 0.4.0
New Features
Consumable Items
- Added first-class consumable item support. Mark items with
is_consumableto enable one-time-use effects (potions, scrolls, buff foods). - Consumable fields:
consume_duration(status duration, -1 = permanent),consume_max_stacks(stacking cap, -1 = unlimited),consume_percentage(% of max resource),consume_absolute(flat resource amount). - Consume order: apply status → percentage resources → flat resources → reduce quantity.
- Same item type produces the same status ID, enabling automatic stacking via the existing status system.
- Added
item_consume_before/item_consume_afteremitters.item_consume_beforeis cancellable (return false to prevent consumption). - Added
item_consume_before/item_consume_afteraction script slots on item templates. - Added
inventory.reduceItemQuantity(item, amount)helper method. - ItemCard tooltip now displays consume effects (restore/reduce) with color coding and duration.
Custom Vue Directives
- Added
v-persistdirective for keeping images in browser memory cache. Prevents decoded image data from being evicted when elements are removed from DOM (e.g., panels usingv-if). Usage:<img :src="iconPath" v-persist />. Cache holds up to 600 entries with automatic FIFO eviction. - Added
v-fitdirective that auto-shrinks font size so text fits within its container. Reacts to text changes and container resizes. Usage:<div v-fit>{{ name }}</div>. Supports{ min }option for minimum font size.
Trait Merge Mode
- Added
is_mergeflag to character trait schema. When enabled, trait values accumulate across statuses instead of last-wins. Merge behavior is type-aware:chooseManydeduplicates,arrayconcatenates,schemadeep-merges.
Stat Display Improvements
- Added
reduction_is_goodflag to character stats. When enabled, indicates that reducing the stat is beneficial (e.g., cooldowns). When disabled (default), increasing the stat is beneficial (e.g., health, damage). Used mainly for UI coloring. - Added
colorfield to resource stats. Hex color (without#) used for resource bar fill. - Added
game.registerStatGroupResolver(resolver)method. Registers a function that receives a character and returns an array of stat tag names. The engine builds groups by filtering stats matching each tag, sorts byorder, and resolves display names from locale keygroup.{tag}. Enables per-character stat group control (e.g., show breeding stats only for MC). - Removed
character_stat_groupsstate. Replaced by the stat group resolver pattern above.
Ability Description Auto-Generation
- Added
ingame_descriptionfield to ability definitions (htmlarea, aspect role only). Template for auto-generating player-facing descriptions. Supports[v]for the aspect's value and[sibling_id]for other aspect values in the same effect. - Added
ingame_description_reffield to ability definitions. Dot-path to the display name in fromFile-referenced data (e.g.,"name","traits.name"). Falls back toname, then raw ID. - Added
game.buildAbilityDescription(abilityId, characterId?, isFlat?)method. Returns auto-generated description lines per effect, or flat ifisFlatis true. - Added
namefield to ability template effects and ability modifier effects. Optional display name returned bybuildAbilityDescription.
Ability UI Components
- Added
AbilityCardcomponent for displaying detailed ability information with icon, name, cooldown, resource costs, description, and auto-generated effects. - Added
AbilitiesViewercomponent for listing and selecting character abilities.
Engine-Level Locale System
- Added native Locale tab under General in the editor. Locale entries have
idandvalfields, supporting|placeholder|syntax for dynamic content. - Added
game.getLine(lineId, params?)method. Looks up a locale entry by ID and substitutes|placeholder|tokens with provided params. Returns[lineId]if entry not found.
Plugin Script Loading
- Breaking Change: Removed automatic script loading from plugins. Plugins must now explicitly specify scripts to load via the
scriptsfield inplugin.json. This enables proper ES6 module usage without double-loading imported files.
Data API
- Changed
game.getData()return type toany. Most paths return a Map, but plugin single-file configs return plain objects. game.drawFromPool()now returnsPoolDrawResult[]({ id: string, quantity: number }[]) instead of flatstring[]. Duplicate draws are stacked by quantity, eliminating manual counting loops in callers.
Editor Schema Field Improvements
- Added "Hide empty" checkbox to both
schemaandschema[]field headers in the editor. "Hide empty" hides fields with undefined, null, empty string, or empty array values. The setting is global and persists across sessions.
Template Variants (~N)
- Templates (
$template_id) now support automatic random variant selection. Add numbered variants with~2,~3, etc. suffixes (e.g.,$greeting,$greeting~2,$greeting~3). When|$greeting|is resolved, the engine picks one at random. No special syntax needed from the caller.
Component Registration API
- Added
game.registerComponent(name, component)method. Allows plugins to register reusable Vue components towindow.engine.components. - Added
game.getComponent(name)method. Retrieves a registered component by name(you can still access them via window.engine.components).
Settings API
- Added
game.getEngineSetting(key)andgame.setEngineSetting(key, value)methods. Read and write engine-level settings (music_volume, sound_volume, font_size, typing_speed). Shared across all games, persisted to browser localStorage. - Added
game.getGameSetting(key)andgame.setGameSetting(key, value)methods. Read and write per-game settings defined in game_settings.json. Persisted in save files.
Narrative System
- Added data-driven narrative composition system. Define slots (insertion points), states (matching conditions), tags (categorized value pools), and segments (text fragments with conditions). The engine selects the best-matching segment at runtime based on live game state.
- Slots are referenced in any text with
|@slotId|syntax — resolved inline through the standard text pipeline. Slots can nest other slots for hierarchical composition. - States support two modes: gate (hard filter — mismatch eliminates the segment) and identity (soft preference — match increases priority, mismatch ignored). Supported types:
boolean,number,range,chooseOne,chooseMany. - Identity states with
chooseManyuse per-overlap scoring — each matching tag adds specificity independently, so a segment matching 3 of 5 tags ranks higher than one matching 1. - Selection pipeline: collect by slot → filter by gates → score identity → keep top specificity tier → anti-repeat (last 2 per slot) → weighted random → resolve content.
- Added
game.registerNarrativeState(id, evaluator)method. Registers a function that provides runtime values for state matching. - Added 4 editor subtabs under Narrative: Tags, Slots, States, Segments.
- Added
fromFileType: 'values'option forchooseOne/chooseManyschema fields. Readsitem.valuesarrays instead ofitem.id— enables tag-based option lists filtered by category.
Plugin Documentation System
- Plugins can now ship their own documentation.
Built-in Plugins
- Added Auto Battler plugin — an ATB combat system with grid positioning, token-based status effects, AI decision-making, and autocast. Includes full documentation.
- Added Gender & POV plugin — dynamic text substitution with two subsystems: POV (1st/2nd person) for the main character, and Gender (masculine/feminine pronouns) for NPCs.
Action Naming Convention
- Breaking Change: All built-in action names standardized to
snake_case. Renamed:joinParty→join_party,leaveParty→leave_party,createCharacter→create_character,updateCharacter→update_character,deleteCharacter→delete_character,addStatus→add_status,addSkinLayer→add_skin_layer,removeSkinLayer→remove_skin_layer,addItemSlot→add_item_slot,removeItemSlot→remove_item_slot,equipItem→equip_item,unequipItem→unequip_item,consumeItem→consume_item,addItem→add_item,learnRecipe→learn_recipe,choicesOver→choices_over.
Art Manager Improvements
- Improved character Art Manager popup with game-accurate preview. Shows game padding zone and character-sheet boundary line scaled to rendered image size.
- Art offset (art_dx, art_dy) is now draggable directly on the character image.
v0.3.0
Version 0.3.0
New Features
HTML Editor
- HTML editor forms now support adding local images using relative paths (e.g.,
assets/games_assets/my_game/_core/images/icon.webp).
Template Validation
- Custom Vue Components are now validated at registration time. Includes tag mismatch analysis to help find mismatched HTML tags.
Character API
- Breaking:
character.getStat(name)now returns a number directly instead of a reactive ref. If you were usingcharacter.getStat("health").value, change it tocharacter.getStat("health"). - Added
character.getStatRef(name)for when you need the reactive ComputedRef (e.g., forwatch())
Inventory API
- Breaking:
inventory.getCurrencyAmount(id)renamed toinventory.getItemQuantity(id)
Data API
game.getData(path, original?)now returns a deep copy of the data by default, making it safe to modify without affecting the source. Passtrueas the second parameter to get the original data for better performance (read-only).
Random Pools
- Added
game.drawFromPool(entry, settings?)for weighted random selection using editor-defined pool entries with filters, entity groups, and multiple selection modes. - Added
game.drawFromCollection(collection, settings?)for simple weighted draws from any array or Map without defining pool templates. - For more details see 'Random Pools' documentation.
Constant Properties
- Properties can now be marked as constant (
is_constant: true) in the editor. - Constant properties cannot be modified at runtime and are excluded from save files.
- Use for game constants that modders can override (e.g., base stats, multipliers, config values).
Game Events
room_enter_beforeandroom_enter_afterevents now receivedungeonIdas a second parameter.
Localhost Development
- Added standalone development server (
dev-server.cjs) for running the engine in localhost mode without Electron. - Run
npm run dev-serverto start the data API server, thennpm run startfor the Vite dev server.
Docs
- Added Dungeons API documentation.
- Updated exported components documentation with grouped categories and detailed prop descriptions.
UI
- Improved Character Sheet UI with flexible stat groups layout and resource bars for resource-type stats.
Bug Fixes
- Fixed choices not appearing for text-type dungeons in scene mode.
- Plugin array data is now converted to Map format in the data registry, consistent with other data types. Use
getData("path").get(id)to access items. - Fixed resource values not clamping to new max when a status is removed and
can_overflowis false. - Fixed plugin
schema[]fields showing numeric indexes (0, 1, 2) instead of property IDs as field labels. - Fixed
fromFileTypeAndandfromFileTypeOrnot working for plugin-generated forms. - Fixed file search not matching paths with Windows-style backslashes.
v0.2.0
Version 0.2.0
Breaking Changes
Character API
createCharacterFromTemplate()has been removed. UsecreateCharacter()instead, which now accepts both template objects and template ID strings.// Before game.createCharacterFromTemplate("npc_1", "npc_template"); // After game.createCharacter("npc_1", "npc_template");
Item API
createItemFromTemplate()has been removed. UsecreateItem()instead, which now accepts both template objects and template ID strings.// Before game.createItemFromTemplate("iron_sword"); // After game.createItem("iron_sword");createInventoryFromTemplate()has been removed. UsecreateInventory()with optional second parameter instead.// Before game.createInventoryFromTemplate("shop_template"); // After game.createInventory("shop_id", "shop_template");
New Features
Character API
createStatus()accepts both template objects and template ID strings.// From template ID const buff = game.createStatus("strength_buff"); // From custom object const custom = game.createStatus({ id: "my_buff", stats: { strength: 5 } });addToParty(),removeFromParty(), anddeleteCharacter()now accept both Character instance and ID string.game.addToParty("alice"); // Using ID string game.removeFromParty("bob"); // Using ID string game.deleteCharacter("npc_1"); // Using ID string
Changelog Viewer
Click on the engine version number in the main menu to view the changelog.
File Browser
- Added "Clear Cache" button in file picker dropdown to refresh file list after adding new assets.
- Added "Resources/File Browser" documentation page.
UI Improvements
- Improved UI for the Editor Form Buttons.
- Added key filtering for schema fields - filter input appears when schema is expanded.
Character Templates
- added
starting_statusesfield to Character Templates Form for adding statuses on character creation.
Bug Fixes
File Browser
- Fixed WebP auto-conversion triggering on typed search strings instead of only on selected files.
UI
- Fixed UI styling issues when OS dark mode is enabled.