Repository navigation
Hand be-music skins the data from issues #239–#260 - #265
Merged
Merged
Conversation
Add every field, type, and opt-in flag the skin SDK v2 needs (#239-#260) in one go so skins can be written against a stable interface while the host fills the values in over the following commits. - Raise BE_MUSIC_SKIN_API_VERSION to 2 and keep accepting revision 1. - Move the play options shape into the SDK as BeMusicPlayOptions; PixiPlayOptions and its unions stay as aliases. - Add resolveBeMusicSkinSettings / validateBeMusicSkinSettings (hooked into validateBeMusicSkin) and binJudgeTimings, with tests and bench cases. - Compile the host through: bindings read the new draws* flags, frames carry empty measure lines, no BGA layers, default settings, and empty song images / records; select wires setPlayOption, cyclePlayOption, and toggleReadme to the existing play option and readme code. - Document the new fields, opt-ins, decide screen, skin settings, song images, and the timing sign convention in English and Japanese.
Log every judgement the engine reports with a timing offset and pass it to skins: the latest offset as runtime.lastJudgeDeltaMs, the last 64 as runtime.judgeTimings for timing meters, and the whole play as the result data's judgeTimings for histograms (#240, #253). The recent window lives in a ring buffer whose oldest-first view is only rebuilt when a judgement arrives, so reading it every frame allocates nothing. Judgements without a timing (autoplay hits, missed notes) are skipped, as the SDK documents.
…ents Pass be-music skins the rest of the per-play scalars the v2 runtime declares: - song: the chart being played (#239). - minBpm / maxBpm / mainBpm: worked out once per chart from its tempo points, mainBpm being the BPM held longest (#245). - songLengthMs / songElapsedMs: the song length and the audio-clock playhead, clamped to the length so the remaining time never goes negative through the audio tail (#246). - failedAtMs / allNotesDoneAtMs / outroAtMs: latched when the gauge fails mid-play, the last note is judged, and the chart ends (#247). - gaugeRiseAtMs / gaugeMaxAtMs: latched when the gauge first rises above its start and first tops out (#260). - scoreHistory / gaugeHistory: the live sample arrays, by reference (#252).
Stamp each lane's last press and release on the play clock, autoplay hits included, and hand them to be-music skins as the lane frame's pressedAtMs / releasedAtMs (#255). Mark notes and long notes the engine plays itself with autoplay: every note under autoplay, and the scratch lanes' notes under auto-scratch, mirroring the engine's single auto-scratch flag (a replay's recorded flag wins) (#260).
Fill the result data's lamp, gaugeType, judgeRuleset, and fast / slow counts (#249). The lamp comes from a new resolveClearLamp helper: FAILED when the gauge failed, MAX / PERFECT / FULL_COMBO when every note was judged PERFECT / GREAT / GOOD or better, and otherwise the lamp of the gauge the play cleared on (DEATH and HAZARD count as EX_HARD).
… skins The gameplay runtime now carries laneCovers (SUDDEN+ / HIDDEN+ per play side, as the scene's shutter draws them), visibleMs, and greenNumber, worked out the way beatoraja's duration readouts are but over the core view's pixel scroll. A skin that sets drawsLaneCover draws the covers itself and the scene stops drawing its own.
A skin that sets drawsMeasureLines now receives frame.measureLines, one line per run of adjacent lanes so double play's gap stays clear, and the scene skips its own lines. Other skins keep the scene's lines and an empty array.
Bombs fired by a PERFECT or GREAT carry that judgement as judge; the bomb an autoplayed long-note head fires has none.
The select frame's pointer now follows the mouse in the stage's design pixels (undefined off the stage), so hit() reports hover for the rect under it.
…untimes Push long-note-start / long-note-end UI commands wherever a hold begins and ends (autoplay, auto-scratch, manual play), carrying the mode the ruleset plays the note in and whether it completed or was dropped. Add PlayerOptions.onRulesetResolved so a host can read the judge windows the engine actually uses, including a replay's recorded settings. The TUI worker now only clears the POOR BGA on clear-poor-bga instead of on any unrecognised command.
…c skins Track the engine's long-note start / end commands per lane to label each drawn long note with its mode and state (pending, held, dropped, done) and to fill the lanes' holding / holdStartedAtMs. Convert the ruleset windows the engine reports at launch into the runtime's judgeWindows; a mid-chart #EXRANK change is not reflected.
A skin's setPlayOption accepted any numeric value under an unknown key, growing the play options with junk, and cyclePlayOption indexed past the choices for a step other than 1 or -1. Both now go through pure helpers that reject unknown keys and non-finite numbers and clamp the step to one choice either way.
The core select scene takes a resolveSongRecord option and the select binding answers frame.songRecord(song) with it. Without the option every song still reads as never played.
Each played (not autoplayed or replayed) LR2/default-family result folds into the chart's best record: best lamp, EX score and max combo, fewest BAD + POOR, and a play count. Records are keyed by the chart-file SHA-256 (falling back to the chart path), persisted in localStorage under be-music-demo.song-records, and served to the select skin through resolveSongRecord.
… and back images Select, gameplay and result frames now resolve songImage from a per-scene ImageBitmap cache that loads lazily on the first miss, remembers missing images, closes the least recently used bitmap past 32 and everything on dispose. Select bumps its frame revision when an image arrives.
…e it A skin that sets drawsBga now receives frame.bga with the active base, layer and POOR images (decoded bitmaps, the playing video element, or cropped copies of #BGA sub-regions) and poorActive, and the player stops compositing its own BGA for it. The docs spell out that bmson and video layers arrive unkeyed and that #ARGB tints are left to the skin.
DefaultPixiDecideView draws the skin's decide screen through a new BeMusicDecideBinding for its durationMs (default 2000), with the chart, the play options, and song images. Enter, Space or a click skip ahead and Escape cancels back to select, as on the LR2 decide scene.
…e play When the active be-music skin defines decide (and the play doesn't run on a beatoraja theme), the demo mounts DefaultPixiDecideView between select and gameplay, preloading the play behind it as the LR2 decide path does. Skins without one go straight to the play as before.
The gameplay, select, result, and decide scenes take the player's stored setting values through a beMusicSkinSettings option and setBeMusicSkinSettings, check them against the skin's schema with resolveBeMusicSkinSettings, and hand them to the skin as frame.settings from the next frame. Select also bumps its revision so skins that cache per revision redraw. Without stored values skins keep drawing with their defaults. resolveBeMusicSkinSettings is re-exported from the skin subpath for hosts building a settings UI.
A Skin settings folder is built from the active skin's settings schema (choice as a dropdown, number as a slider with its unit in the name, toggle as a checkbox), rebuilt when the skin changes, and hidden for skins that declare none. Values are stored per skin id in localStorage and reach the scenes on screen at once; a reset button goes back to the defaults.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
be-music-player-demo | 05c5c7a | Commit Preview URL Branch Preview URL |
Oct 11 2026, 03:41 PM |
…ripping The benchmarks import the sources through Node's strip-only TypeScript support, which rejects parameter properties, so the head benchmark crashed on JudgeTimingLog's constructor.
The skin SDK is not a public API yet, so the additions stay in revision 1 instead of starting a revision 2. The changesets are renamed and reworded to match.
nulltask
force-pushed
the
feature/skin-sdk-complete-frames
branch
from
October 11, 2026 15:40
dfef35c to
05c5c7a
Compare
Exports Benchmark
Summary
Top Regressions
Top Improvements
Excluded (sub-timer-resolution)Per-call latency at or below 0.001ms on at least one side — the reported time is measurement noise, not the case's real cost, so no percent change is shown.
Newly Skipped
|
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.
Summary
Settles the be-music skin SDK interface: every capability asked for in #239–#260 is now reachable from the frames, even where no built-in skin uses it yet. The SDK is not a public API yet, so the skin API stays at revision 1.
Compatibility
drawsMinespattern:drawsLaneCover,drawsMeasureLines,drawsBga.Gameplay
song(Skin SDK: hand the gameplay frame the song entry (level, subtitle, difficulty, rank, total) #239): the chart being played.lastJudgeDeltaMsandjudgeTimings(Skin SDK: expose the last judgement's timing offset and a timing history #240, Skin SDK: judge timing visualizer / distribution data #253): judgement offsets. Positive is late, negative is early.minBpm/maxBpm/mainBpm(Skin SDK: min / max / main BPM on the gameplay frame #245).songLengthMs/songElapsedMs(Skin SDK: elapsed / remaining time and song length during play #246).failedAtMs/allNotesDoneAtMs/outroAtMs(Skin SDK: STAGE FAILED and outro state on the gameplay frame #247).scoreHistory/gaugeHistory(Skin SDK: live score / gauge history for a pacemaker graph #252).gaugeRiseAtMs/gaugeMaxAtMs(Skin SDK: autoplay note style and gauge-rise / gauge-max timers #260).laneCovers,visibleMs,greenNumber(Skin SDK: expose lane cover / lift / hidden and the green number #241).measureLines(Skin SDK: hand measure line positions to skins #242).judgeWindows(Skin SDK: judgement window widths #259).pressedAtMs/releasedAtMs(Skin SDK: per-lane key on / off times #255) andholding/holdStartedAtMs(Skin SDK: judgement on bombs and an LN-hold effect #251).mode/state(Skin SDK: long-note state (held / dropped) and CN / HCN #250). Bombs:judge(Skin SDK: judgement on bombs and an LN-hold effect #251). Notes and long notes:autoplay(Skin SDK: autoplay note style and gauge-rise / gauge-max timers #260).bga(Skin SDK: let skins draw the BGA #257), filled for skins that setdrawsBga.Select
playOptions,playOptionChoices, and thesetPlayOption/cyclePlayOption/toggleReadmeactions (Skin SDK: let select skins read and change play options #243).pointer, andhit()now returns whether the pointer is inside the rect (Skin SDK: pointer hover and cursor position #258).songRecord(song)(Skin SDK: per-song clear lamps on select rows #248). The demo keeps the best record per chart in localStorage.Result
lamp,gaugeType,judgeRuleset,fast,slow,judgeTimings(Skin SDK: richer result data (lamp kind, gauge type, FAST / SLOW) #249, Skin SDK: judge timing visualizer / distribution data #253).All screens
songImage(song, kind)returns the STAGEFILE / BANNER / BACKBMP image (Skin SDK: STAGEFILE / BANNER / BACKBMP images #254).settings: a skin can declare a settings schema; the player hands it the chosen values on every frame, and the demo's Debug Menu edits and persists them (Skin SDK: skin-defined settings (custom options, files, offsets) #244).New screen
BeMusicSkin.decide(Skin SDK: a decide screen #256): an optional decide screen between select and play.Shared engine (
@be-music/player)long-note-start/long-note-end.PlayerOptions.onRulesetResolvedcallback carrying the resolved judge windows.SDK helpers
binJudgeTimingsandresolveBeMusicSkinSettings/validateBeMusicSkinSettings.Docs (
docs/be-music-skin.mdand.ja.md) and changesets are updated.Known limitations
judgeWindowsreflects the windows at chart start; a mid-chart#EXRANKchange is not reflected.visibleMs/greenNumberuse the 1P cover and the current BPM, ignoring#SCROLL/#SPEED.liftis always 0 because the player has no lift option.CLEARselect sort does not use the records yet.Closes #239, closes #240, closes #241, closes #242, closes #243, closes #244, closes #245, closes #246, closes #247, closes #248, closes #249, closes #250, closes #251, closes #252, closes #253, closes #254, closes #255, closes #256, closes #257, closes #258, closes #259, closes #260
Test plan
pnpm run check: format, lint and types.pnpm test: 2413 tests.🤖 Generated with Claude Code