Repository navigation
Version Packages - #59
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
changeset-release/main
branch
7 times, most recently
from
October 23, 2026 05:52
cc3ce10 to
2d7649e
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
October 23, 2026 09:12
2d7649e to
4c3d215
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@typeonce/ritmo-browser@0.11.0
Minor Changes
778301a: Draining a service is declared, checked at compile time, and observed at run time.
System.Consumes<"snapshot">marks the methods of a service type that drain it. A system that declares the service withGame.System.servicesees those methods as a compile error naming the fix (snapshot() drains Game/Controls: declare it with Game.System.consumeService, or read the resource its capture writes);Game.System.consumeServicecalls them. Its other methods stay available throughservice(a settings screen can stillrebind).Keyboard.Actions,Keyboard.Presses,Gamepad.Actions,Gamepad.Pads,Touch.Actions,Pointer.Actions,Controls.Device,Controls.Controls,Controls.Pool, andInputCapture.Source. Breaking: a system that calledsnapshot()throughGame.System.serviceno longer compiles; read the resourceInputCapture.systemwrites instead (or, for a system that must drain the source itself, declare it withGame.System.consumeService).Game.System.servicereturnsServiceRead<D, false>andconsumeServicereturnsServiceRead<D, true>.service-consumed-twicelint also reports what a debug runtime saw: a consumed service whose consumers ran at more than one placement in the session, in unnamed schedules too (Game/Drain (schedule#2, step 0, unnamed)). Named schedules are reported once, by the static lint.866bcff:
Controlsmerges several input devices into one source, every device snapshot shares one shape, and capturing input is checked at compile time and bydescribe().Controls.actions({ buttons: [...] })declares the game's actions once. Itsmerge({ keyboard, gamepad, touch })builds one input source: an action isheldwhile any device holds it,pressedwhen any device pressed it, andreleasedwhen a device released it and none holds it now (the same rules as one device with all the bindings). Each device binds every declared action; a device that cannot is passed aslacking(device, ["pause"]), which must name exactly the actions it lacks (autocomplete offers them). A device binding an undeclared action, a device missing an action, and an action no device binds do not compile.actions,vectors,available(whether the last used device binds each action, so a game can offer a lacking action another way),devices(each device's own snapshot, keyed by the merge names: sticks, triggers, touch aim), andlastUsed(nulluntil a device is used). The merged source exposesrebind.<device>for devices that can rebind, anddispose().idle({ keyboard: Keyboard.idle(...), ... })initializes the input resource. Reading an action on a device that lacks it does not compile.Keyboard.Snapshotis{ actions, vectors, used }. Readinput.actions.jump.pressedinstead ofinput.jump.pressed. Hand-written timelines and bindings are unchanged.Touch.Snapshotrenamesbuttonstoactions(each still aButtonStatewithaimandcancelled) and replacestouchedwithused. Readinput.actions.fireinstead ofinput.buttons.fire.Layout.buttonsandView.buttonsare unchanged.Keyboard,Gamepad,Pointer,Touch) hasused: something pressed in this window or held, or a stick or swipe in use. Releases do not count, so a blur or a disconnect does not changelastUsed.Game.System.consumeService(Service)declares a service a system drains on each run (an input source'ssnapshot(), a message queue); it is the only way to call the draining methods (seeSystem.Consumes).describe()warns with the newservice-consumed-twicelint when a consumed service has more than one placement, for example the same input captured in a fixed and a render schedule, where each capture sees only the presses since the other ran.SystemDescription.consumeslists them.InputCapture.systemkeeps its declared access: it consumes the source and writes the resource, so aGame.System.thisTickreader of the resource placed before the capture no longer compiles (the error names both systems and the fix: place the reader after the capture), and capturing one source twice is reported bydescribe(). The exportedInputCapture.CaptureSystemtype is removed: let the system's type be inferred.Debug.serviceLintslets a service value add findings todescribe()lints, reported asservice-lintwith the service as subject;Controlsuses it to list every device that lacks an action.deaed5a:
Gamepadreads controllers as named actions, and keyboard and gamepad bindings can change at runtime.Gamepad.actions(window, bindings, options)binds actions to buttons named by the browser's standard mapping ("south","west","rightTrigger","dpadUp","start"...). Each snapshot hasactions(asKeyboard's), everybuttonsstate by name whatever the bindings (for menu navigation and for a settings screen waiting for the button to bind), theleftandrightsticks with a radial dead zone (ygrows down, asTouch's stick), how far each trigger is pulled, and the followed pad'sid. It follows the pad used last and releases everything held when that pad disconnects. It reads the pads on every animation frame (and on every snapshot) untildispose(), so taps shorter than an update count with nothing to call; the lifecycle issnapshot/rebind/dispose, as forKeyboard.Gamepad.idle,scripted,recording,parseTimeline, andisButtonmatch the other devices; timelines name buttons, so a replay follows the bindings, and an entry'sconnectedrecords a disconnect or reconnect, so a replay drops and regains the pad on the same frames.rebind(bindings)onKeyboard.actions(...)andGamepad.actions(...)binds the same actions to new keys or buttons, for players who remap their controls. An action held only through a key it lost reportsreleased; a key held across the rebind holds a newly bound action only after a fresh press, so an action never turns held without a press. It takes exactly the actions the source was made with: an extra action fails to compile with<name> is not an action of these bindings, even from an object not written inline. Scripted keyboard actions ignore it (their timeline names actions).Keyboard.presses(window)reports every key that went down since the previous snapshot, bound or not, as{ key, code }, so a settings screen can wait for the key to bind (the gamepad equivalent is the snapshot'sbuttons).Keyboard.parseRebinding(bindings, data)andGamepad.parseRebinding(bindings, data)validate saved bindings (for example fromlocalStorage) as aResultready forrebind, with the offendingpathon failure.pressandreleaseof one entry is now a release then a new press when it was held, and still a tap when it was up, sorecording(...)captures a quick double tap and the replay matches.InputCapture.systemrequires the resource's value type to be exactly the source's snapshot type. A resource that only matched both ways (for example one with an extra optional field) now fails with a message naming the fix: declare it asDescriptor.TransientResource<InputCapture.SnapshotOf<typeof Source>>().Keyboard.Actionsnow requiresrebind. An object implementing it by hand addsrebind() {}.57430bb: Local multiplayer: players join slots by pressing a button on any pooled device.
Controls.players(Actions, { slots, join, leave })declares the players: the slots (the most at once, in join order), the button action that joins (or"any"for any button), and the one that leaves while joining is open. A misspelled or vector action, no slot, a slot namedjoining, and a button action namedany(it would read asjoin: "any") do not compile.pool(devices)on the declared players builds one input source from devices nobody holds yet. Pressing join on one gives it the first free slot; that snapshot reportsevent: "joined"with the player's input idle, so the press does nothing else (joining with the jump button does not jump). One device holds one slot; slots keep their names when another empties, and the next player fills the gap. Each pooled device binds every action (or is passed throughlacking), and the join action always.input.p2):joined,device(what it joined with),connected,event("joined","left", ornull), and the player'sactions,vectors, andavailable, idle while the slot is empty. A device's own snapshot is behind a check ofdevice(if (input.p2.device === "pads") input.p2.devices.pads.left). The declared players'idle(...)initializes the resource.setJoining(open)(closed at the start: open it on a join screen, or once for drop-in play),seat(slot, device)(a player in from the start, or picked on a menu: a pool name, or{ pads: index }for one pad; returns{ ok: false, reason }when the slot or device is taken),leave(slot)(a pause menu), andrebind.<device>through a plain service. Leaving by button works only while joining is open; otherwiseleaveis an ordinary action.Gamepad.pads(host, bindings, max)reads every pad on its own, for a pool: each pad joins as its own player. An unplugged pad keeps its slot (connected: false) and returns to it when it comes back; two pads of one model share an id, so they are told apart by their place in the pad list.Gamepad.scriptedPads(bindings, timelines)drives one timeline per pad in tests.describe()lints two keyboard devices that bind the same key, a device taken by two sources (a merge and a pool), and joining never opened.97e39c5:
Pointerbinds actions likeKeyboardandGamepadand merges intoControls, andControls.groupmakes devices used together count as one.Pointer.track(target, blurHost, options?)isPointer.actions(target, blurHost, bindings, options?). Bindings map actions to buttons ({ fire: ["primary"] }); a misspelled button or an empty list does not compile. Pass{}for position and raw buttons only. ThePointer.Pointertype isPointer.Actions<B>andTrackOptionsisActionsOptions.{ actions, vectors, buttons, position, movement, wheel, over, locked, used }. Readinput.buttons.primary(or a bound action) instead ofinput.primary.usedis a press, a hold, or movement over the element; releases do not count.movement,wheel, andlockedcome with the wheel and the pointer lock.backandforwardside buttons (PointerEvent.button3 and 4) are reported and bindable.Pointer.buttonNamesandPointer.isButtonlist and check them.rebind(...)swaps the buttons of the same actions;Pointer.parseRebinding(bindings, data)validates saved ones.Pointer.idle(bindings, position?)andPointer.scripted(bindings, timeline, start?)take the bindings first. Timelines still name buttons, so a replay follows the bindings as live play does.actions.group({ keyboard, mouse })groups devices into one merge entry: it binds what its devices bind between them, andlastUsedandavailablefollow it as a whole, so walking with the keyboard while aiming with the mouse stays on one device. Grouped devices keep their own names indevices,rebind, andidle;lastUsednames the group. Pass the group tolacking(...)for actions none of its devices binds. A group of fewer than two devices, alackingdevice or a group inside a group, a device binding an undeclared action, and a name used twice across devices and groups do not compile.bb371d4:
Pointerreads the wheel and the pointer lock, andTouch.trackisTouch.actions, so every device is built the same way.wheelis how far it scrolled since the previous snapshot, in CSS pixels (lines and pages are converted), for zoom. ThewheelUp,wheelDown,wheelLeft, andwheelRightbuttons pulse (pressed and released in one window) once perPointer.wheelStep(100 px, one notch), so a stepping action binds them (nextWeapon: ["wheelDown"]) and merges with a gamepad bumper inControls. The page does not scroll over the element unlesspreventScroll: false.PointerTargetgains thewheellistener.movementis the raw motion since the previous snapshot (movementX/movementY), locked or not. Withlock: document, a click on the element locks the pointer (the browser grants a lock only inside a click, so the module asks in its own listener); that click is not reported as a press.lockedturnsfalsewhen the player leaves with Escape, which releases what was held.usedalso counts motion and scrolling.wheel,movement, andlocked. Theirpress/releasetakePointer.PhysicalButtons; wheel pulses come fromwheel.Touch.track(target, blurHost, layout, options?)isTouch.actions(...), theTouch.Touch<B>type isTouch.Actions<B>, andTouch.TrackOptionsisTouch.ActionsOptions, matchingKeyboard.actions,Gamepad.actions, andPointer.actions. Arguments and behavior are unchanged.b3b16fd: Movement is one vector action: declared once, bound per device, merged by
Controls, and checked like every other action.Controls.actions({ buttons: [...], vectors: [...] })declares both kinds. A button action is pressed and held (jump); a vector action is a direction with length at most 1,ygrowing down (move). A name declared as both kinds does not compile. The declared names are listed in the set'sbuttonsandvectors.Keyboard.vector({ up, down, left, right })binds a vector to keys: opposite keys cancel and a diagonal has length 1.Gamepad.vector({ stick, up, down, left, right })binds it to a stick, the d-pad, or both (the longer wins). ATouchlayout'sstick.vectornames the vector the stick drives. Every device snapshot hasvectors; readinput.vectors.move(Pointer's is empty: it reportspositionandmovement).Controlsmerges a vector as the longest any device reports, so devices never add up past 1 or cancel each other. Vectors work withlacking,available,idle, andlastUsed(a vector in use counts as use). Binding a vector action as a button (or the reverse), missing a vector, and readingactions.moveorvectors.jumpdo not compile, each with an error naming the fix.rebindandparseRebindingtake vector bindings (Gamepadcan swap sticks).Keyboard timelines name a vector by direction,
press: ["move.right"];parseTimelinevalidates it. Button actions are named as before.Breaking:
Touch.idleandTouch.scriptedtake the names as an object,{ buttons, vector? }(vectoras the layout'sstick.vector):Directions that step one tile at a time (a grid game reading taps) stay button actions.
Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).@typeonce/ritmo@0.11.0
Minor Changes
778301a: Draining a service is declared, checked at compile time, and observed at run time.
System.Consumes<"snapshot">marks the methods of a service type that drain it. A system that declares the service withGame.System.servicesees those methods as a compile error naming the fix (snapshot() drains Game/Controls: declare it with Game.System.consumeService, or read the resource its capture writes);Game.System.consumeServicecalls them. Its other methods stay available throughservice(a settings screen can stillrebind).Keyboard.Actions,Keyboard.Presses,Gamepad.Actions,Gamepad.Pads,Touch.Actions,Pointer.Actions,Controls.Device,Controls.Controls,Controls.Pool, andInputCapture.Source. Breaking: a system that calledsnapshot()throughGame.System.serviceno longer compiles; read the resourceInputCapture.systemwrites instead (or, for a system that must drain the source itself, declare it withGame.System.consumeService).Game.System.servicereturnsServiceRead<D, false>andconsumeServicereturnsServiceRead<D, true>.service-consumed-twicelint also reports what a debug runtime saw: a consumed service whose consumers ran at more than one placement in the session, in unnamed schedules too (Game/Drain (schedule#2, step 0, unnamed)). Named schedules are reported once, by the static lint.866bcff:
Controlsmerges several input devices into one source, every device snapshot shares one shape, and capturing input is checked at compile time and bydescribe().Controls.actions({ buttons: [...] })declares the game's actions once. Itsmerge({ keyboard, gamepad, touch })builds one input source: an action isheldwhile any device holds it,pressedwhen any device pressed it, andreleasedwhen a device released it and none holds it now (the same rules as one device with all the bindings). Each device binds every declared action; a device that cannot is passed aslacking(device, ["pause"]), which must name exactly the actions it lacks (autocomplete offers them). A device binding an undeclared action, a device missing an action, and an action no device binds do not compile.actions,vectors,available(whether the last used device binds each action, so a game can offer a lacking action another way),devices(each device's own snapshot, keyed by the merge names: sticks, triggers, touch aim), andlastUsed(nulluntil a device is used). The merged source exposesrebind.<device>for devices that can rebind, anddispose().idle({ keyboard: Keyboard.idle(...), ... })initializes the input resource. Reading an action on a device that lacks it does not compile.Keyboard.Snapshotis{ actions, vectors, used }. Readinput.actions.jump.pressedinstead ofinput.jump.pressed. Hand-written timelines and bindings are unchanged.Touch.Snapshotrenamesbuttonstoactions(each still aButtonStatewithaimandcancelled) and replacestouchedwithused. Readinput.actions.fireinstead ofinput.buttons.fire.Layout.buttonsandView.buttonsare unchanged.Keyboard,Gamepad,Pointer,Touch) hasused: something pressed in this window or held, or a stick or swipe in use. Releases do not count, so a blur or a disconnect does not changelastUsed.Game.System.consumeService(Service)declares a service a system drains on each run (an input source'ssnapshot(), a message queue); it is the only way to call the draining methods (seeSystem.Consumes).describe()warns with the newservice-consumed-twicelint when a consumed service has more than one placement, for example the same input captured in a fixed and a render schedule, where each capture sees only the presses since the other ran.SystemDescription.consumeslists them.InputCapture.systemkeeps its declared access: it consumes the source and writes the resource, so aGame.System.thisTickreader of the resource placed before the capture no longer compiles (the error names both systems and the fix: place the reader after the capture), and capturing one source twice is reported bydescribe(). The exportedInputCapture.CaptureSystemtype is removed: let the system's type be inferred.Debug.serviceLintslets a service value add findings todescribe()lints, reported asservice-lintwith the service as subject;Controlsuses it to list every device that lacks an action.Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).@typeonce/ritmo-devtools@0.11.0
Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).@typeonce/ritmo-math@0.11.0
Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).@typeonce/oxlint-plugin-ritmo@0.11.0
Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).@typeonce/ritmo-pixi@0.11.0
Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).@typeonce/ritmo-render@0.11.0
Patch Changes
Session.makewithouthostClock,Replay.session, thePointerrebind) are fixed;Debug.Lintlists every lint code the runtime emits. Thestale-targetlint's fix now namesqueries.x.getHandle(handle).