@vectojs/danmaku-core
v0.3.0
Published
Pure danmaku (barrage) engine primitives — object pool, kinematic lane scheduler, motion presets, and timeline track — renderer-agnostic and zero-dependency.
Readme
@vectojs/danmaku-core
Pure danmaku (barrage / 弹幕) engine primitives — renderer-agnostic and zero-dependency.
@vectojs/danmaku-core owns the simulation half of a danmaku system: the object
pool, the lane-assigning scheduler with kinematic collision avoidance, the motion
presets, and the timeline track for video-synced playback. It intentionally has no
VectoJS, Canvas, DOM, or browser dependency, so a website, benchmark, CLI, or server
can drive the same danmaku semantics and plug in any renderer.
It was extracted from the Bakudan playground, where it
sustains 5,000 concurrent danmaku by keeping all per-frame work O(N) and allocating
nothing on the hot path.
What's in the box
DanmakuPool— a fixed-capacity object pool. Slots are reused, never re-allocated, so a full stress run produces no per-frame garbage.Scheduler— advances every active slot in oneO(N)pass and assigns lanes with a three-pass strategy (free lane → gap + kinematically-safe lane → round-robin fallback) so scrolling danmaku never visually overlap. Text content is injected via atextSampler, so the engine carries no wording of its own.PRESETS— eight pure motion functions (scroll,reverse,top,bottom,sine,rotation,glitch,repulsion). Each is aPresetFnthat mutates a slot givendtand pointer state — no rendering, no side effects.DanmakuTrack— a cursor over timestamp-pinnedTimedDanmakuEntryitems that emits the danmaku due since the last video time, for playback synced to a<video>.generateTimedTrack— a Gaussian-clustered demo-track generator (70% of entries cluster around peak moments) that also takes an injectedtextSampler.
Renderer-agnostic by design
The engine only reads and writes plain numbers on PoolSlot (x, y, width,
rotation, opacity, age, lane, charAngles). It never touches a canvas. Your
renderer walks scheduler.pool.slots each frame and draws the active ones however it
likes — Canvas2D, WebGL/MSDF, SVG, or a test harness that asserts positions.
Install
bun add @vectojs/danmaku-coreUse
import { DanmakuPool, Scheduler } from "@vectojs/danmaku-core";
// You own the content. The engine just asks for the next string.
const phrases = ["hello", "666", "nice", "first!"];
const textSampler = () => phrases[(Math.random() * phrases.length) | 0]!;
const pool = new DanmakuPool(5000);
const scheduler = new Scheduler(pool, 1920, 1080, 500, { textSampler });
// In your frame loop (dt in ms, and the active motion preset):
function frame(dtMs: number) {
scheduler.tick(dtMs, "scroll", {
cursorX: 0,
cursorY: 0,
pointerActive: false,
});
for (const slot of pool.slots) {
if (!slot.active) continue;
// draw slot.params.text at (slot.x, slot.y) in your renderer of choice
}
}Jelly squash/stretch
const pool = new DanmakuPool(20_000);
const scheduler = new Scheduler(pool, width, height, 5_000);
scheduler.showcaseJelly = true;
// Optional application impulse, for example after drag release.
scheduler.exciteJelly(slot.id, 0.45);Renderers read jellyScaleX and jellyScaleY from each active PoolSlot and apply
them as squash/stretch multipliers. The deterministic spring integration allocates
nothing per frame. Setting showcaseJelly to false resets every active slot to the
exact neutral state (jellyScaleX = 1, jellyScaleY = 1, and jellyVelocity = 0) on
the next scheduler tick.
Video-synced playback
import {
createDefaultParams,
DanmakuTrack,
generateTimedTrack,
} from "@vectojs/danmaku-core";
const track = new DanmakuTrack(generateTimedTrack(15, { textSampler }));
// On a discontinuous jump (load, scrub), reposition without firing:
track.seek(video.currentTime);
// Each frame during normal playback, get entries due in (lastTime, currentTime]:
for (const entry of track.sync(video.currentTime)) {
scheduler.userSpawn({
...createDefaultParams(),
text: entry.text,
preset: entry.preset ?? "scroll",
});
}Verify
bun install
bun run format:check
bun run lint
bun test
bun run buildRelease governance
Public API changes start with a Changeset, then the reviewed version bump is published
through the repository's exact @vectojs/danmaku-core@<version> tag workflow.
CHANGELOG.md remains the human-readable release record.
License
MIT © 2026 Xuepoo
