npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

miaoda-game-scenario-core

v0.3.0

Published

Engine-agnostic story / event-script runtime: a safe expression evaluator, a compact text DSL, variable memory, and a pausable runner with label/goto jumps, if/else branches and player choices. No rendering, no engine dependency. Backs visual-novel / AVG,

Readme

miaoda-game-scenario-core

Use this engine-independent runtime for branching dialogue, cutscenes, quests, and event scripts. Authors provide line-oriented text; the runtime parses labels, conditions, choices, variables, jumps, and blocking host commands.

Install

pnpm add miaoda-game-scenario-core

Script and run

import { Memory, ScenarioRunner } from 'miaoda-game-scenario-core';

const script = `
label start
say Guard "Halt. Toll is 5 coin."
choice "Pay" -> pay if coin >= 5
choice "Leave" -> leave
label pay
set coin = coin - 5
say Guard "Pass."
exit
label leave
say You "Another time."
exit
`;

const runner = new ScenarioRunner({
  memory: new Memory({ coin: 8 }),
  commandExecutor: async (ctx) => {
    if (ctx.name === 'say') await dialogue.show(ctx.args[0], ctx.args[1]);
  },
  choiceHandler: (options) => menu.present(options),
});
runner.load(script);
await runner.run();

Built-in control statements are label, goto, if/elseif/else/endif, set, choice, and exit. Other verbs such as say, show, and playBgm go to your commandExecutor. Command arguments are unquoted strings; convert numeric values yourself. Return a promise to pause until the host operation finishes.

Choice options are already filtered by their guards. Resolve with the option's index, not an index from the full authored list. Unknown commands are no-ops, so explicitly validate script verbs if a typo should fail authoring.

Cross-engine dialogue presentation

DialoguePlayer provides the engine-independent reveal/page/advance state machine. Pass a string containing explicit \f page breaks, or an array of pages measured by the host UI. Drive update(deltaSeconds) from Phaser/Cocos update or a React timer, subscribe once to render snapshot.visibleText, and bind one input action to advance().

const dialogue = new DialoguePlayer({ charactersPerSecond: 30 });
const unsubscribe = dialogue.subscribe((view) => renderText(view.speaker, view.visibleText));

dialogue.start({ speaker: 'Guard', text: ['Halt.', 'State your business.'] });
dialogue.update(deltaSeconds);
dialogue.advance(); // reveal page, then next page, then complete

The player deliberately does not measure fonts, create timers, subscribe to input, or own engine objects. Concurrent start, invalid timing, and resetting active dialogue throw instead of silently replacing presentation state. This makes the same contract suitable for Phaser, Cocos, React DOM, tests, and coding-agent generated hosts.

Save and restore

Memory.toJSON() is suitable for chapter/label saves; restore it and call run(startLabel). For exact continuation, save runner.snapshot at a quiescent statement boundary, load the same script, call loadSnapshot(snapshot), then resume(). Snapshots waiting on a command or choice are rejected because host UI work cannot be safely replayed. The script signature detects content mismatch but is not authentication.

Public API

DialoguePlayer, parse, compile, analyzeScenario, evaluate, evaluateCondition, Memory, ScenarioRunner, CommandContext, ChoiceOption, and scenario statement types are exported. The runtime does not render dialogue, create timers, play audio, or own a scene.