Source of truth: mods/sdk/modding.d.ts (generated from the Rust contracts by
cargo run -p modding --example write_sdk). This page is checked against this
file by node scripts/check-modding-docs.mjs: every symbol in a heading
(###) must exist in the SDK, and every function or event in the SDK must
appear here. For the exact fields of a type, the .d.ts is authoritative; for the
detailed rules and limits, see also TypeScript mods: the detailed guide.
Conventions: song times are in microseconds (timeUs), except
Hit.offsetMs (real milliseconds). Layout and style lengths
are fractions of the screen height. Any out-of-range value throws an
exception in the script (see concepts).
Availability by script kind
Three kinds of scripts share the same API: a mod (defineMod, main.ts),
a skin (defineSkin, skin.ts) and a map script (defineChart,
script.ts). Phases: load (top level of the file), setup, event.
| Function | mod | skin | map script | Phase |
|---|---|---|---|---|
defineMod / defineSkin / defineChart |
defineMod |
defineSkin |
defineChart |
load, once only |
ctx.on |
yes | yes | yes | load, setup; afterwards |
ctx.has, ctx.element |
yes | yes | [TO VERIFY] | setup, event |
game.* (reads) |
yes | yes | yes | setup, event |
hud.*, storage.* |
yes | yes | no | setup, event |
stage.* |
stage permission |
stage permission |
stage permission |
setup, event |
playfield.*, lanes.set |
no | yes | yes | setup, event during a play session |
judgements.register, scrollSpeed.register, ratings.register, tables.register, library.filters.register, controls.register, gameplay.register |
yes | no | no | setup only |
ratings.backfill, metron.performance, ui.popup, ui.dismiss |
yes | no | no | setup, event |
leaderboard.query, leaderboard.best |
yes | no | no | setup, event (response: leaderboard.result) |
tabs.register, tabs.extend |
yes | no | no | setup only |
downloads.register, downloads.registerBridge |
network permission |
no | no | setup only |
skinImport.register |
skinImport permission |
no | no | setup only |
skinImport.stage, skinImport.create, skinImport.panel, skinImport.close |
skinImport permission |
no | no | setup, event |
metron.catalog, metron.difficulty |
yes | yes | yes | setup, event |
log.info, log.warn |
yes | yes | yes | always (even at load) |
Sources: crates/modding/src/api.rs (for_running, for_mods_and_skins,
playfield_call, *Register functions).
Declaration
defineMod
defineMod(input: ModDefinition): ModManifest. Called once, at the top
level of main.ts, with export default. Fields: id, name, version,
apiVersion (must be 2), and optional author, description,
homepage, fonts, uses, provides, permissions, images, loadOrder,
setup(mod). Any unknown field is rejected. Rules for each field:
Declaring the mod: defineMod.
defineSkin
defineSkin(input: SkinDefinition): SkinManifest, in skin.ts. Same fields
as a mod except loadOrder; setup(play: PlayContext). See
Skins.
defineChart
defineChart(input: ChartDefinition): ChartManifest, in script.ts next to
a chart. Fields: apiVersion, permissions?, images?, setup(play). No
id. See Map scripts.
ctx
Typed global ctx (not a parameter: name the setup parameter play or
mod, never ctx).
ctx.on(event, handler): subscribes a handler to an event from the list below; allowed at the top level and insetup.ctx.has(id): true if the packageid, declared inuses, is present, compatible and running.ctx.element("<package>/<element>").configure(options): configures an element that another package provides (provides.elements). The package must be inuses, otherwise an exceptionadd `<package>` to `uses` to configure its elementsis thrown.
Events
Subscription: ctx.on("<name>", handler). game.* events are only delivered
to scripts running during the play session; queues are bounded (1024) and an
event may be dropped if the mods thread is running late.
game.songStart
SongStartEvent: song, judgements (JudgementConfig), timeUs (negative
during a pre-roll). The chart and the playfield become readable.
game.judgement
JudgementEvent: column, timeUs, offsetUs (input − note; 0 for a
miss), tier (TierRef), combo, counts (per tier), accuracy. The
totals are those of the engine after this note.
game.pause
SongTime: { timeUs }.
game.resume
SongTime: { timeUs }.
game.tick
TickEvent: timeUs, hitCount (native non-Miss hits, reset to zero at
the start of each play session). 30 Hz, only while a chart is playing without pause.
game.beat
Beat: index, timeUs, bpm, meterBeat (0 on the first beat of the
measure). Delivered at the time of each beat of the chart; if late, only the
last one.
game.songEnd
SongEndEvent: counts, maxCombo, accuracy, aborted.
game.playfieldChange
Payload: Playfield (resize).
game.settingsChange
Payload: ModSettings.
elements.configure
ElementConfigure: element, from ("player" or the id of the calling package),
options (all options, defaults filled in). Received by the provider.
controls.action
ModActionEvent: id (declared local id), pressed, timeUs.
library.ready
LibraryReady ({}): the library is loaded (after the interface and the mods).
library.chartAdd
ChartAdded: { chartId }, one per chart added by a scan.
library.chartsAdded
ChartsAdded: { chartIds, truncated } (8192 at most): the same ids in a
single event.
ratings.progress
RatingProgress (to the requesting mod only): requestId, id, done,
total, failed, finished, error?, ahead.
metron.performanceResult
PerformanceResult (to the requesting mod only): requestId, calculator,
value?, unit (pp or SSR), error?.
ui.action
PopupActionEvent: { id } of a popup action, or "dismiss".
leaderboard.result
LeaderboardResult (to the requesting mod only): requestId, chartId,
offset, total, entries (LeaderboardEntry[]), judgement,
performance? (LeaderboardPerformance), unavailableReplays, error?.
skinImport.opened
SkinImportOpened (permission skinImport): importerId, locale
(en, fr, or zh), source? (SkinSource), error?.
skinImport.staged
SkinImportStaged: requestId, stageId?, report? (Report), error?.
skinImport.created
SkinImportCreated: requestId, id? (id of the written skin), error?.
skinImport.action
SkinImportAction: the { id } of a card action (skinImport.panel) or
"dismiss".
Reads: game.*
On-demand reads from setup or a handler. Never while the mod loads
(is not available while the mod loads).
game.song
game.song(): Song | null: the last song started (title, artist,
creator, difficulty, mode, layout, keys, durationUs, noteCount,
holdCount, bpm, timing, ratings indexed by calculator id).
game.notes
game.notes(query: NoteQuery): NotePage. fromUs, toUs (head in
[fromUs, toUs)), column?, cursor?, limit? (default 64, 1 to 256). Pagination
via next. endUs is null for a tap note; mines are not notes.
game.hits
game.hits(query: HitQuery): Hit[]: the latest native non-Miss hits, from the
oldest to the most recent (limit 1 to 256, default 50). Hit = { offsetMs, tier }
(offsetMs is negative when early).
game.playfield
game.playfield(): Playfield | null: keys, lanes ({ column, x, width }),
hitY, spawnY, scrollTimeUs.
game.player
game.player(): PlayerState: playing, paused, timeUs, combo,
maxCombo, counts, judged, accuracy.
game.judgements
game.judgements(): JudgementConfig | null: preset and tiers (TierInfo).
game.settings
game.settings(): ModSettings | null: volume, showFps, scrollTimeMs,
audioOffsetMs, ratingSystem.
HUD: hud.*
A node tree retained per mod; a node stays until hud.remove,
hud.clear, or the mod is disabled. Per-mod limits: 256 nodes, 256
characters per text, id from 1 to 64 characters, 8 levels of groups.
Reusing an id replaces the node (same parent). Fields common to every
node: id, parent?, visible?, layout?, style?, showWhen?, animate?,
element?.
hud.text
hud.text(node: TextNode): string. Own fields: text, bind?
(TextBinding). Bindings: combo, maxCombo, hits, misses, judged,
remaining, accuracy (decimals?), ghostAccuracy, ghostDelta, ghostCombo,
ghostName, tierCount (tier), tierName (tier), lastJudgement
(colors?), elapsed, total, timer, fps, label (label: HudLabel),
action (action: "skipIntro"). The overlay updates them without going back
through the script.
hud.box
hud.box(node: BoxNode): string. Own field: fill? (Fill:
songProgress, accuracy, tierShare + tier) which crops the box
horizontally from the left.
hud.image
hud.image(node: ImageNode): string. src: a PNG from the mod's folder (8 MiB at
most, checked at call time); fit?: contain (default), cover, fill.
hud.group
hud.group(node: GroupNode): string. flow? (Flow: direction row |
column, gap?, align?, justify?) lays out the children; without flow, they
are placed as fractions of the group (so give it width and height).
hud.update
hud.update(patch: NodePatch): void: modifies an existing node; the given
fields of layout and style replace those of the node, the others remain.
text/bind, src/fit, flow, fill only apply to the corresponding node type.
hud.remove
hud.remove(id: string): void: removes the node and its descendants.
hud.clear
hud.clear(): void: removes all of the mod's nodes.
Layout (Layout): x, y (fractions of the parent, [-10, 10]), width,
height ([0, 4], screen heights), anchor (Anchor). Style (Style):
color, background, gradient, border, radius, padding, opacity,
font, size, weight, italic, align, shadow, transform,
transitionMs. Details on the bounds: HUD.
Colors: #rgb, #rgba, #rrggbb, #rrggbbaa.
Native rendering: stage.*
Requires permissions: ["stage"] (Permission is "stage", "skinImport", or "network"); the player can revoke it per mod.
Budgets per script: 128 elements, 16 emitters, 1024 particles, 64
triggers. Elements are declarative: animations and triggers are
evaluated by the renderer on every frame, never by the script.
stage.sprite
stage.sprite(input: StageSprite): string: a PNG image declared in images.
stage.rect
stage.rect(input: StageRect): string: a solid rectangle, radius in screen heights.
stage.text
stage.text(input: StageText): string: a single line (128 characters) in the
game's font.
stage.emitter
stage.emitter(input: StageEmitter): string: a particle emitter (burst,
rate, maxParticles, lifetimeMs…).
stage.trigger
stage.trigger(input: StageTrigger): string: on (StageEvent: judgement,
press, release, holdStart, holdEnd), target, play? or stop?.
stage.play
stage.play(input: StageCue): void: starts an animation from a handler.
stage.stop
stage.stop(input: StageCue): void: stops it.
stage.remove
stage.remove(id: string): boolean: removes an element or a trigger.
stage.clear
stage.clear(): void: removes everything.
Position at (StagePoint): space (screen default, playfield, lane,
receptor), column?, x?, y?. Layers (StageLayer): below, lanes,
above.
Playfield: playfield.* and lanes.set (skins and map scripts)
playfield.set
playfield.set(spec: PlayfieldSpec): void: merges spec into the playfield
(at setup). The fields are listed in the SDK's PlayfieldSpec; see
Playfield reference.
playfield.update
playfield.update(update: PlayfieldUpdate): void: patch (PlayfieldSpec),
transitionMs? (0 to 10000), easing? (Easing). Animated field by field by the
renderer; no per-frame calls.
lanes.set
lanes.set(update: LaneUpdate): void: column (starting from 0), lane
(LaneSpec), transitionMs?, easing?.
Judgement: judgements.register
judgements.register
judgements.register(definition: JudgementSetDefinition): void, setup
only, 8 sets per mod. Fields: id (a-z, 0-9, -, 32 at most), name,
params? (JudgementParam: label, min, max, step, default,
short?), accuracy (SetAccuracy), holds (SetHolds), tiers(params)
(JudgementTiers, synchronous, resolved once per parameter combination right
after setup). A tier (JudgementTier): id, name, color, gradient?,
windowMs? or earlyMs + lateMs, weight?, breaksCombo?. From the tightest
to the widest, with Miss last. Rules:
Judgement sets,
docs/judgement.md.
Scroll speed: scrollSpeed.register
scrollSpeed.register
scrollSpeed.register(definition: ScrollSpeedDefinition): void, setup
only, 8 systems per mod. id, name, param (ScrollSpeedParam, like
JudgementParam), toMs(value, context) (ScrollSpeedToMs) which returns the
scroll time in milliseconds; context.travel (ScrollSpeedContext) is the
reference distance in screen heights. Rules:
Scroll speed.
Gameplay modifiers: gameplay.register
gameplay.register
gameplay.register(input: GameplayModifierDeclaration): void, setup only,
8 per mod. id, name (32), description (200), kind
(GameplayModifierKind: auto, ghost, mirror, random, noLn, fullLn),
group?, icon?, conflictsWith? (8 <modId>/<id> keys). The behavior is
native: the script only declares.
Keys: controls.register
controls.register
controls.register(input: ModActionDeclaration): void, setup only, 32 per
mod. id, name (64), defaultKey: a physical key code accepted by the game
(KeyH, Digit1, Backquote…, list: crates/core/src/mode/key-codes.json).
Received through controls.action. Escape and F2 remain reserved.
Library, tables, and difficulty ratings
tables.register
tables.register(input: TableDefinition): void, setup only. id,
columns (ColumnDefinition: name, kind number | text, indexed?).
16 tables per mod, isolated per mod; their rows disappear along with their chart.
ratings.register
ratings.register(input: RatingDeclaration): void, setup only, 16 views
per mod. id, name, calculator, unit, table, column, version,
panels? (RatingPanel: metrics, bars, radar, timeline, at most 4).
The table must have been declared by this mod, the column must be numeric,
and version must equal the one from metron.catalog().
ratings.backfill
ratings.backfill(input: BackfillRequest): number | null: requests the calculation of
missing or stale charts (charts? restricts to a set of ids). Returns a request id
(null when the mod already has 2 requests in progress); progress arrives through ratings.progress.
library.filters.register
library.filters.register(input: LibraryFilterDeclaration): void, setup
only, 32 per mod. id, name, table, column, version, unit?. The table
must already be declared by this mod.
metron.catalog
metron.catalog(): MetronCatalog: calculators (MetronCalculator: id,
performance, version).
metron.difficulty
metron.difficulty(input: CalculatorRequest): number | null: preloaded native
value for the current chart, null if absent.
metron.performance
metron.performance(input: PerformanceRequest): number | null: calculator,
accuracy (fraction [0, 1]). Returns a request id (null if the service
is busy); the result arrives through metron.performanceResult. Only osu-2018
(pp) and etterna-515 (SSR) have a performance.
ui.popup
ui.popup(input: PopupDefinition): void: menu progress window
(title, detail, done, total, actions? up to 4 PopupAction { id, label }).
One popup open per mod, 8 in total; the text is never HTML.
ui.dismiss
ui.dismiss(): void: closes the mod's popup.
Local leaderboard: leaderboard.*
Read-only access to a chart's local leaderboard, no permission required: typed, bounded,
asynchronous (sources: Local leaderboard: leaderboard.*,
crates/modding/src/leaderboard.rs, api.rs). chartId is the library's chart
identifier (library.chartAdd, library.chartsAdded). Reserved for
mods; never a path, a file, or a replay entry.
leaderboard.query
leaderboard.query(input: LeaderboardQuery): number | null: chartId, limit?
(1 to 100, 20 by default), offset? (0 to 100,000). Returns a request number,
or null when they pile up (2 pending per mod, 16 in total); the
response arrives through the leaderboard.result event. Each request rejudges the
chart: read one page at a time.
leaderboard.best
leaderboard.best(input: LeaderboardBestQuery): number | null: { chartId }.
Equivalent to query with limit: 1: rank 1 of the entire leaderboard (imported
replays included, received).
A LeaderboardEntry: rank (starting at 1), replayId, playerName? (null
for an old replay without a name), received, accuracy (percent),
performance? (null if not computable), performanceNonstandard, maxCombo,
misses, tiers (TierCount[]: { name, count }), rate, modified,
playedAtMs. The leaderboard is the one shown in the interface: replays rejudged with the player's
current judgement, ranked by performance.
Selection tabs: tabs.*
Declarative: a mod adds a tab to the selected chart's panel, or sections to
existing tabs; Rust validates everything, and the interface renders generically (no
markup from the mod). setup only, mods only (sources:
Tabs in the selection screen: tabs.*, tabs.rs).
tabs.register
tabs.register(input: TabDeclaration): void: id, title (1 to 24 characters),
icon? (closed list of 16 lucide icons: layers, info, trophy, star,
gauge, activity, chart-bar, list, flame, music, clock, target,
sparkles, bookmark, heart, users), order? (0 to 1000), panels
(1 to 8 TabPanel). 4 tabs per mod, 12 in total.
tabs.extend
tabs.extend(input: TabExtensionDeclaration): void: tab (ExtendableTab:
info, leaderboard, mods), slot (TabSlot: top | bottom), order?,
panels (1 to 4). Adds content before or after the host's content, never
removing it. 4 extensions per mod, 24 in total.
Panels (TabPanel): metrics, bars (max?), radar (max?) with
TabField entries (label, source, unit?, decimals?), timeline (like the
notes), leaderboard (title, limit 1 to 10: the already-evaluated ranking of
the Leaderboard tab) and text (title?, text 1 to 280 characters, no
markup). Field sources (TabFieldSource): { kind: "column", rating, column } (a numeric column from the table of a difficulty view declared
by the same mod with ratings.register, earlier), { kind: "chart", metric }
(ChartMetric), { kind: "leaderboard", stat } (LeaderboardStat: plays,
bestPerformance, bestAccuracy). Tabs follow the mod live.
Downloads: downloads.*
network permission. Requires permissions: ["network"]; setup only; the player can revoke the
permission mod by mod and sees the declared hosts. The script never
touches the network: the host (crates/downloader) sends all requests, over HTTPS and
to declared hosts only, and keeps the player's token out of the script's reach.
Sources: Downloads: downloads.register and
Mods and downloads on the feature/mod-download-sources branch,
crates/modding/src/downloads.rs (4 declarations per mod, 64 in total).
downloads.register
downloads.register(input: DownloadDeclaration): void: a declarative source
(data only). Fields: id, name, description?, site?, kind
(DownloadKind: mirror = one more mirror for osu! beatmapsets,
target: "osu"; source = one more tab in Download), hosts (1 to 8
exact lowercase DNS names), rateLimit? (requests per minute, 1 to 120, 30 by
default), auth? (DownloadAuth: kind none | token, header?, scheme?,
scope? download | all; the token is entered by the player), search?
(SearchSpec: url, params, sorts, statuses, paging, response) and
download (DownloadSpec: url with {id}, or urlField = JSON path of
the URL). URL templates: closed set of variables {query}, {status}, {sort},
{offset}, {page}, {limit}, {cursor}, {keysMin}, {keysMax}, {starsMin},
{starsMax}, {bpmMin}, {bpmMax}, {lengthMin}, {lengthMax}, {genre},
{language}; a [ …] group is only written if all of its variables have a
value. Responses: simple JSON paths (ItemSpec, DifficultySpec, OnlySpec,
ResponseSpec with format osu | mapped, PagingSpec offset | page |
cursor).
downloads.registerBridge
downloads.registerBridge(input: BridgeDefinition): void: for an API that the
data cannot describe (POST, XML/HTML, search → detail →
file chaining). Pure, synchronous functions compute the next step
(BridgeStep), the host makes the request, then calls the mod back:
search(query, page, state)(BridgeSearch): first page,pageis 1;onResponse(response, state)(BridgeOnResponse):responseis aBridgeResponse(status,headers,text);action?(result, actionId, state)(BridgeAction): click on a button of a result (BridgeActionResult).
A step (BridgeStep) contains one of: request (BridgeRequest: url,
method GET | POST, headers?, body?, form?, json?, expect? json |
text | xml | html), results (BridgeResult[]: id, title, artist,
creator?, coverUrl?, tags?, size?, keyCount?, details?
(ResultDetail), actions? (ResultAction), data?), download
(BridgeDownload: url, method?, headers?, body?, form?, json?,
filename?, format BridgeFormat: zip | osz | qp) or error; plus
nextPage? and state? (JSON passed back as-is on the next call). Limits: 6
requests per search or action, 60 s, 2 MiB of text per response. An
exception, a non-JSON result or a budget overrun disables the bridge.
BridgeDeclaration is the form without the functions.
Skin import: skinImport.*
skinImport permission. A mod with permissions: ["skinImport"] offers to convert an osu! skin
(folder or .osk) into a Prism skin. It touches no file and no pixel: the host reads,
transforms the images, and writes the folder; the mod only supplies values.
Mods only; register in setup, the rest in a handler. Without the
permission: `skinImport` needs the `skinImport` permission. Full example:
mods/skin-converter (pvng.skin-converter). Source: Converting an osu! skin: the skinImport permission,
docs/convertisseur-skin-osu.md, crates/modding/src/importers.rs.
skinImport.register
skinImport.register(input: ImporterDeclaration): void: id, name,
description?, localized? (ImporterText per language), sources
(SourceKind[]: folder, archive). Adds a card to the Skins page; 4
importers per mod. The button opens the game's file dialog.
skinImport.stage
skinImport.stage(input: StageRequest): number | null: sourceId, skin
(SkinDescription: id, name, version?, author?, description?,
playfield?, layouts (SkinLayout: keys, playfield?, lanes), hud?
(SkinHud)), images (ImageOp[]: source, dest, animation?, frame?,
padTop?, padBottom?, fit? (ImageBox), stretch? (ImageSize)). The host
validates, transforms and replies with skinImport.staged (Report: files
StagedFile, totalBytes, budgetBytes, overBudget, problems).
skinImport.create
skinImport.create(input: SkinImportCreate): number | null: { stageId };
the host writes a readable skin.ts into <data>/skins/ (never replacing an
existing skin) and replies with skinImport.created.
skinImport.panel
skinImport.panel(input: ImportPanelDefinition): void: a declarative card (text
only): title, status (PanelStatus: working, ready, done,
error), detail?, sections? (PanelSection: heading, rows of
PanelRow { label, value }), notes? (PanelNote: level NoteLevel
info | warning | error, text), actions? (PanelAction: id, label,
primary?, editSkin? = id of a skin created by this mod, opens the editor). Limits:
6 sections of 24 rows, 48 notes, 4 actions.
skinImport.close
skinImport.close(): void: removes the card.
The source read (SkinSource): sourceId, label, kind, ini? (SkinIni:
general IniGeneral, mania ManiaSection[] of ManiaColumn, fontsUsed,
problems), images (SourceImage: normalized name, frames, hasStill,
scale, dimensions, bytes, blank?, peakColor?, peakWidth?),
truncated, totalBytes, problems.
Storage: storage.*
JSON file specific to the mod; 256 KiB, 256 keys of 1 to 128 characters; written at most once per second.
storage.get
storage.get(key: string): unknown | null.
storage.set
storage.set(entry: StorageEntry): void: { key, value }, JSON value.
storage.remove
storage.remove(key: string): void.
storage.keys
storage.keys(): string[]: sorted keys.
storage.clear
storage.clear(): void.
Log: log.*
log.info
log.info(text: string): void: message displayed under the mod in the Mods page
(1024 characters at most, 256 messages queued).
log.warn
log.warn(text: string): void: same, warning level.
Secondary types
SDK types that the sections above mention without detailing them (the .d.ts
gives every field):
| Type | Meaning |
|---|---|
Animate |
{ on: AnimateOn, kind: AnimateKind, durationMs }: HUD node animation; AnimateOn = judgement | miss | hit, AnimateKind = pop | popFade | flash; duration 1 to 5000 ms |
Border |
{ width, color } (solid border of a node) |
Gradient, GradientStop |
{ angle, stops } (2 to 8 stops { color, at }, at in [0, 1]) |
Shadow |
{ x, y, blur, color } |
Transform |
{ x?, y?, scale?, rotate? } applied after placement |
TextAlign |
start | center | end |
ShowWhen |
paused | running | showFps | judged | ghost |
HudElement |
name of a standard element whose font the player changes: fps, accuracy, hits, misses, combo, timer, remaining, status, judgement, judgementCounts |
HudAction |
skipIntro (action binding) |
ImageFit |
contain | cover | fill |
FlowDirection, FlowAlign, FlowJustify |
fields of Flow: row | column; start | center | end | stretch; start | center | end | spaceBetween |
Lane |
{ column, x, width } of a Playfield |
Note |
{ index, column, timeUs, endUs? } of a NotePage |
TimingPoint, BpmRange |
{ timeUs, bpm, beatUs, meter }; { min, max, main } (in Song) |
HitList |
Hit[] |
HostEvents |
event name → payload table that types ctx.on |
Dependency, DependencySpec |
value of uses: semver range, or { version, required?, feature? } |
Provides, ElementDeclaration, OptionKind |
provides.elements; { root?, options? }; number | integer | boolean | string | color | enum | colors |
ModSetup, SkinSetup, ChartSetup |
setup signatures (ModManifest / PlayContext) |
SetDeclaration, ScrollSpeedDeclaration |
declaration shapes without the function (tiers, toMs) |
ColumnKind |
number | text (table column) |
ChartMetric |
notes, holds, holdPercent, averageNps, peakNps, bpmMin, bpmMax, duration |
RatingField, RatingFieldSource, RatingSeries, RatingSeriesSource |
fields and series of a RatingPanel: { label, source, unit?, decimals? }, { kind: "column" } or { kind: "chart" }, density | bpm |
SpriteSize |
{ width, height } (screen heights) |
PlayfieldAnchor, ScrollDirection, HitLightOn, TextureFilter, JudgementLineAt, MeasureLineLength, HoldMissedStyle |
enumerations of PlayfieldSpec and LaneSpec |
StageAlign, StageSpace, StageSize, StageProps, StageAnimation, StageJudgementFilter, StageColumn |
pieces of stage.*: alignment, coordinate space, size, animated properties (x, y, scale, rotation, opacity, color), animation { durationMs, easing?, repeat?, from?, to? }, trigger filters |
AuthKind, AuthScope |
DownloadAuth.kind: none | token; scope: download | all |
DownloadTarget, PagingKind, ResponseFormat |
osu (the only mirror target); offset | page | cursor; osu | mapped |
BridgeMethod, BridgeExpect |
GET | POST; json | text | xml | html |