Skip to content

Repository files navigation

Pulse

Pulse is a small, strictly typed absolute-time sequence runtime for Roblox. It compiles reusable event and sampled timelines, then evaluates them from finite source samples supplied by a host. The core has no clock, scheduler, frame loop, or discontinuity policy.

Pulse owns sequence-local anchors, playback speed, ordered event traversal, reverse callbacks, loop identity, explicit addressing, cleanup generations, and lifecycle. Hosts own the meaning of time and presentation side effects. An optional ClockDriver adapts scheduling-capable clocks to the same raw core.

Raw host-driven playback

local Pulse = require(ReplicatedStorage.packages.pulse)

type HitContext = {
	worldPosition: Vector3,
}

local makeBuilder = Pulse.builder :: () -> Pulse.SequenceBuilder<HitContext>
local builder = makeBuilder()
local sequence = builder
	:duration(1.5)
	:event({
		time = 0.2,
		run = function(playback, context)
			print("hit", context.worldPosition, playback:getPosition().timePosition)
		end,
	})
	:sample({
		startTime = 0.2,
		endTime = 1.0,
		run = function(_playback, timePosition, _unwrappedTimePosition, rate, _context)
			-- Absolute sampled state; no dt accumulation.
			print(timePosition, rate)
		end,
	})
	:compile()

local playback = Pulse.playback(sequence, {
	worldPosition = targetPosition,
})

playback:play({ position = 100, rate = 1 })
playback:evaluate({ position = 100.5, rate = 1 })
playback:evaluate({ position = 101, rate = 1 })

TimeSample.position is an absolute source coordinate. Pulse maps its displacement through playbackSpeed; it never multiplies displacement by TimeSample.rate. The rate is atomic metadata used for direction and reported to active samplers after local speed is applied.

Optional clock-driven playback

local Tempo = require(ReplicatedStorage.packages.tempo)

-- tempoProvider is the host adapter in docs/guides/tempo-integration.md.
local driver = Pulse.clockDriver(tempoProvider(clock), "heartbeat" :: "heartbeat", {
	forward = Tempo.Enums.Direction.forward,
	backward = Tempo.Enums.Direction.backward,
})

local raw = Pulse.playback(sequence, {
	worldPosition = targetPosition,
})
local driven = driver:attach(raw, {
	discontinuityMode = "reconstruct",
})

driven:play()

attach transfers exclusive temporal-control ownership to driven until driven:detach(); do not mutate raw while it is attached. One driver may serve many playbacks. Attachments lazily share one phase binding while an exact Sample is active, reverse movement is poised to enter a Sample at its excluded end, or an outward zero-distance loop join awaits actual source movement. Ordinary event-only playback keeps only one next-boundary deadline. The attachment must explicitly select "skip", "reconstruct", or "cancel" for provider discontinuities; reusable Sequences contain no such decision.

For late materialization, choose initialMode = "skip" and use onAddress to establish host-owned resources at the exact addressed position without replaying historical one-shot events.

Pulse has no package dependency on Tempo. A Tempo scheduling binding can be adapted at the host boundary; any scheduling-capable clock satisfying ProviderClock may be injected. Destroying a driver never destroys its borrowed clock.

Development

Install the pinned Rokit tools, then use the guarded verification scripts:

rokit install
pesde install
.\scripts\verify\tests.ps1
.\scripts\verify\analyze.ps1
.\scripts\verify\stylua.ps1
.\scripts\verify\selene.ps1
.\scripts\verify\benchmark.ps1
npm install
npm run docs:build

Start with the documentation overview, the getting-started guide, or the API reference.

Solver V2 verification

CLI and editor use Luau solver V2 with pinned Luau-LSP 1.70.1. Public methods and frozen callback payloads are read-only. PlaybackPositionSnapshot describes frozen callback positions; PlaybackPosition remains writable for writePositionInto and caller-owned position copies. SequenceAddress is an input view; build a mutable local record before passing it if needed. Parameterless generic builders use a concrete constructor specialization as shown above.

./scripts/verify/analyze.ps1
./scripts/verify/analyze.ps1 -Project dev.project.json -Sourcemap dev-sourcemap.json -Paths src,dev
./scripts/verify/type-errors.ps1 -OutDir .verification/type-errors-fresh
./scripts/verify/tooling-tests.ps1
./scripts/verify/test-harness.ps1

LUAU_LSP_OVERRIDE can select a patched executable matching the pin. Captures in .verification/ record path, version, SHA-256, definitions hash, native exit, and both output streams. Source and accepted public contracts form the static gate; dynamic Lune fixtures run as behavior proofs. Negative contracts must fail at their marked expressions without unexpected diagnostics.

Tests and benchmarks use unique owned .pulse-tests/ runtimes, protected cleanup on success or failure, concurrent-run isolation, and bounded abandoned-run recovery. Pass -KeepRuntime to retain a specific runtime for inspection. Cached historical material is outside these checks.

About

Generic sequence playback runtime for Roblox VFX, skill timelines, cutscenes, and plugin previews.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages