Skip to main content

1.3.0 — TypeScript-first architecture

This is a major release. The .rblua DSL still works, but all new projects are TypeScript-first.


TypeScript replaces .rblua

Components, pages, and layout are now written in TypeScript. The compiler runs typescript-to-lua (TSTL) on every .ts file and appends the Lua output directly into the build pipeline — no extra build step needed.

src/
layout.ts ← window config
pages/
1_Home.ts ← tab layout (Next.js-style)
combat/
SpeedHack.ts ← component (SolidJS-style)
misc/
globals.lua ← plain Lua still works

SolidJS-style reactive signals

// Like SolidJS createSignal — read by calling, write with .set()
const enabled = signal(false)
const speed = signal(16)

enabled() // → false
enabled.set(true) // → triggers all watchers

SolidJS-style event hooks via on.*

// Like createEffect scoped to RunService.Heartbeat
on.heartbeat({ when: enabled }, () => {
const h = _PulseGetHumanoid()
if (h) h.WalkSpeed = speed()
})

on.signal(enabled, (v) => {
// fires whenever enabled changes
})

on.respawn(() => {
// CharacterAdded
})

Next.js-style file-based pages

// src/pages/1_Home.ts
definePage('Home', { icon: 'house' }, () => [
groupbox('left', 'Player', { icon: 'person', mount: 'SpeedHack' }),
groupbox('right', 'Visuals', { icon: 'eye', mount: 'PlayerESP' }),
])

Pages are auto-registered in filename order. No routing, no URL params — just tabs.

Fluent widget builders

defineComponent('SpeedHack', () => {
const enabled = signal(false)
const speed = signal(16)

on.heartbeat({ when: enabled }, () => {
const h = _PulseGetHumanoid()
if (h) h.WalkSpeed = speed()
})

return [
toggle('Speed Hack').bind(enabled),
slider('Walk Speed', { min: 16, max: 250 }).bind(speed),
]
})

One layout.ts for all window config

// src/layout.ts
export default {
title: 'MyScript',
toggleKey: 'RightControl',
size: [850, 560] as [number, number],
uiLibrary: 'windui' as 'windui' | 'linoria',
theme: 'Indigo',
icon: 'code-2',
folder: 'MyScript',
acrylic: true,
transparency: 0.8,
} satisfies LayoutConfig

CDN-only runtime

The Pulse runtime and adapter are always loaded from CDN. They are never inlined into the output. The compiled script is just the re-execution guard + two loadstrings + your code:

if _G.__AOT_R_DESTROY then pcall(_G.__AOT_R_DESTROY) end

local _P=loadstring(game:HttpGet("https://cdn.pulse-rb.dev/v1.3.0/bundle.lua"))()
local _A=loadstring(game:HttpGet("https://cdn.pulse-rb.dev/v1.3.0/adapters/windui.lua"))()

-- your compiled TypeScript below
defineComponent("SpeedHack", function() ... end)

Previously, the entire runtime (~90 KB) was inlined. Now it's ~5 KB of user code.


Obfuscator: Ironbrew2 via .NET CLI

The obfuscator now clones github.com/Trollicus/ironbrew-2 and runs the pre-built IronBrew2 CLI.exe via dotnet --roll-forward Major. No Python Lua runtime needed. Stale/broken clone directories are detected and wiped automatically.


rb init generates TypeScript projects

rb init my-script now scaffolds a full TypeScript project with:

  • src/layout.ts — window config
  • src/pages/1_Home.ts — home tab
  • src/combat/SpeedHack.ts — example component
  • src/visuals/PlayerESP.ts — example component
  • tsconfig.json — TSTL-compatible config
  • CLAUDE.md / AGENTS.md / PULSE_DOCS.md — AI context files

.rblua projects still build without changes.