The pack
sketch
function sketch(): Pack<{ sketch: SketchApi }>
The sketch pack. Pure logic, so it needs no backend and runs headless.
Helpers
builtinGestures
function builtinGestures(): Record<string, XY[]>
A starter set, generated rather than typed out as point lists: circle,
triangle, square, line, vee, caret, zigzag, arrow and spiral. Feed them
straight to learn, or teach your own instead.
createPlayback
function createPlayback(strokes: Stroke[], o: PlaybackOptions = {}): Playback
Creates a Playback that replays the given strokes.
createRecognizer
function createRecognizer(): Recognizer
Creates an empty Recognizer. Teach it shapes with learn or learnBuiltins.
createSketchPad
function createSketchPad(o: SketchPadOptions = {}): SketchPad
Creates a SketchPad that turns pointer samples into strokes.
learnBuiltins
function learnBuiltins(rec: Recognizer): Recognizer
Every built-in taught in one call.
simplify
function simplify(points: readonly XY[], tolerance = 2): XY[]
Ramer–Douglas–Peucker: drop every point that is within tolerance of the
line its neighbours already describe.
This is the one that matters. A pointer stroke is hundreds of points a couple
of pixels apart; a vector outline wants a dozen, a Box2D hull wants at most
eight, and the beam's dwell only reads as a corner if there IS a corner.
strokeBounds
function strokeBounds(points: readonly XY[]): Bounds
The axis-aligned bounds of a point set. Returns all-zero bounds for an empty list.
Functions
centroid
function centroid(points: readonly XY[]): XY
The mean of the points. Returns (0, 0) for an empty list.
isClosed
function isClosed(points: readonly XY[], tol = 24): boolean
Did the stroke come back to where it started?
resample
function resample(points: readonly XY[], n = 64): XY[]
n points spaced evenly by ARC LENGTH along the stroke. The first step of
gesture recognition, and how you make two strokes comparable at all.
rotatePoints
function rotatePoints(points: readonly XY[], angle: number, about?: XY): XY[]
Rotate a point set about a centre.
signedArea
function signedArea(points: readonly XY[]): number
Signed area — positive is clockwise on a y-down screen.
smooth
function smooth(points: readonly XY[], iterations = 1, closed = false): XY[]
Chaikin corner cutting: replace each corner with two points a quarter and three quarters along, once per iteration. Rounds a stroke off without adding the wobble a spline through noisy samples would.
Simplify FIRST, then smooth. Smoothing a raw stroke keeps every tremor and simply makes it a smooth tremor.
strokeLength
function strokeLength(points: readonly XY[], closed = false): number
Total length along a polyline.
Classes
Playback
class Playback
Replays strokes as they were drawn — the AnimAction half of the light-pen cartridges, and the reason a title card can WRITE itself on screen.
visible is a list of partial polylines, so the drawing appears at the pace
it was made (or at a constant rate, which is the better look for a logo).
strokes: Stroke[]— The strokes to replay, in order.speed: number— Multiplier on the recorded timings. Default 1.rate: number— Constant draw rate, in length each second. 0 uses the recorded timings.gap: number— The pause between strokes, in seconds. Default 0.25.loop: boolean— True restarts the replay from the top when it finishes.time: number— Seconds into the replay.done: boolean— True when the replay reached the end (never true whileloopis set).constructor(strokes: Stroke[], o: PlaybackOptions = {})get duration(): number— How long the whole replay takes.update(dt: number): void— Advances the replay clock. Call once per frame with the frame time in seconds.reset(): void— Rewinds the replay to the start.get visible(): XY[][]— The polylines to draw this frame — completed strokes plus a partial one.get head(): XY | null— Where the beam is right now, or null between strokes.
Recognizer
class Recognizer
Wobbler's-tolerant single-stroke shape matching: resample to a fixed count, rotate so the first point sits at a known angle from the centre, scale to a reference box, centre on the origin, then compare point-for-point under a golden-section search over rotation.
Size, position and (by default) rotation do not matter, which is what makes a hand-drawn circle a circle.
readonly gestures: Gesture[]— Every learned template. Several entries can share one name.angleRange: number— Search ±this many radians for the best alignment. Default 45°.learn(name: string, points: readonly XY[]): Gesture— Teach it a shape. Several samples under one name all count.forget(name: string): void— Removes every learned gesture with this name.match(points: readonly XY[], minScore = 0): Match | null— Best match, or null when nothing has been learned.rank(points: readonly XY[]): Match[]— Every gesture scored, best first — for a debug readout.
SketchPad
class SketchPad
Pointer in, strokes out.
const ptr = game.input.pointer;
if (ptr?.down) pad.move(ptr.x, ptr.y, game.time); // begin() is implicit
else pad.end();
readonly strokes: Stroke[]— Every finished stroke, oldest first.live: XY[]— The stroke being drawn right now, as raw samples. Empty when idle.minDist: number— Samples nearer than this to the last one are dropped, in pixels. Default 4.simplifyTol: number— RDP tolerance applied on finish. 0 keeps every sample. Default 2.smoothPasses: number— Chaikin smoothing iterations applied after simplification. Default 1.closeDist: number— Ends within this distance make a closed stroke. 0 never closes. Default 24.minLength: number— Strokes shorter than this are dropped on finish. Default 12.snapClosed: boolean— True snaps a closed stroke's last point onto its first. Default true.onStroke: ((s: Stroke) => void) | null— Called with each finished stroke, after it is added tostrokes.constructor(o: SketchPadOptions = {})get isDrawing(): boolean— True while a stroke is in progress.begin(x: number, y: number, time = 0): void— Starts a new stroke at (x, y).movecalls this for you on the first sample.move(x: number, y: number, time = 0): void— A sample. Starts the stroke if there is not one, so a game can simply call this every frame the pointer is down andend()every frame it is not.end(): Stroke | null— Finish. Returns the stroke, or null if it was too short or absent.cancel(): void— Throw away the stroke in progress without finishing it.undo(): Stroke | undefined— Removes and returns the most recent finished stroke.clear(): void— Removes every finished stroke and cancels the one in progress.reprocess(): void— Re-run simplify/smooth over every finished stroke with the current settings.
Types
Bounds
interface Bounds
An axis-aligned bounding box, as returned by strokeBounds.
min: XY— The minimum corner (left, top).max: XY— The maximum corner (right, bottom).size: XY— The width and height.center: XY— The middle of the box.
Gesture
interface Gesture
A learned gesture template held by the Recognizer.
name: string— The gesture name thatmatchreports.points: XY[]— Normalised: resampled, rotated to its indicative angle, scaled, centred.
Match
interface Match
The result of a gesture match: the winning name, its score and its rotation.
name: string— The name of the matched gesture.score: number— 0..1. Above about 0.8 is a confident match.angle: number— How far the drawn stroke was turned from the template, radians.
PlaybackOptions
interface PlaybackOptions
Options for a Playback: replay speed or a constant rate, the gap between strokes, and looping.
speed?: number— Multiplier on the recorded timings. Default 1.rate?: number— Ignore the recording and lay every stroke down at this length per second.gap?: number— Seconds of pause between strokes. Default 0.25.loop?: boolean— Restart from the top when the replay finishes. Default false.
SketchApi
type SketchApi = typeof kit
game.sketch — every function and type this pack carries, as one namespace.
SketchPadOptions
interface SketchPadOptions
Options for a SketchPad: sample spacing, simplify/smooth strength and close detection.
minDist?: number— Samples nearer than this to the last one are dropped. Default 4.simplify?: number— RDP tolerance applied on finish. 0 keeps every sample. Default 2.smooth?: number— Chaikin iterations applied after simplification. Default 1.closeDist?: number— Ends within this become a CLOSED stroke. 0 never closes. Default 24.minLength?: number— Drop strokes shorter than this on finish. Default 12.snapClosed?: boolean— Snap a closed stroke's last point onto its first. Default true.
Stroke
interface Stroke
One finished stroke from a SketchPad, with its raw samples and derived geometry.
points: XY[]— The finished polyline: simplified, then smoothed. What you draw and bind.raw: XY[]— Every sample as captured, before simplification.times: number[]— Seconds since the stroke began, one per raw point — for replay.closed: boolean— Ends met withincloseDist.length: number— Total length of the finished polyline.bounds: Bounds— The axis-aligned bounds of the finished polyline.duration: number— How long the stroke took to draw, in seconds.
XY
interface XY
A 2D point: x and y in the caller's units (pixels from the pointer, or world units).
x: number— The X position, in the caller's units.y: number— The Y position, in the caller's units.


