Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-test.txt

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install Node dependencies
run: npm ci

- name: Typecheck (tsc --noEmit)
run: npm run typecheck

- name: Run pytest
run: pytest

Expand Down
28 changes: 22 additions & 6 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ is Tailwind CSS, served as a prebuilt static stylesheet
(`static/tailwind.min.css`, regenerated by `scripts/build-tailwind.sh`)
β€” never the runtime Play CDN, whose on-the-fly JIT rescans the DOM on
the main thread and caused sustained frame drops with the 3D highway
(slopsmith-desktop#110). No React, Vue, Svelte, bundler, transpiler, or
TypeScript appears in the core static tree, and no build step runs on
the serve path: the Tailwind build is a maintainer-only one-shot whose
output is committed, so Docker / desktop / end users never build. New
features extend `app.js` and the existing globals (`window.playSong`,
`window.showScreen`, `window.createHighway`, `window.slopsmith`).
(slopsmith-desktop#110). No React, Vue, Svelte, bundler, or transpiler
appears in the core static tree, and no build step runs on the serve
path: the Tailwind build is a maintainer-only one-shot whose output is
committed, so Docker / desktop / end users never build. `tsc --noEmit`
with `allowJs` / `checkJs` is permitted as a CI / dev-time type check
β€” JSDoc type annotations and `// @ts-check` directives in core `.js`
files are encouraged. TypeScript stays a dev-only devDependency; emit
-mode TypeScript, `.ts` / `.tsx` source, JSX, frameworks, and bundlers
remain forbidden in core. New features extend `app.js` and the existing
globals (`window.playSong`, `window.showScreen`,
`window.createHighway`, `window.slopsmith`).

**Non-negotiable rules**

Expand All @@ -63,6 +68,11 @@ features extend `app.js` and the existing globals (`window.playSong`,
and does not re-apply the base reset core already provides. Plugins
MUST NOT load the Tailwind Play CDN (or any runtime CSS JIT) β€” the
same no-CDN, build-free-at-serve rule that binds core binds plugins.
- `tsc --noEmit` with `allowJs` / `checkJs` is permitted as a CI /
dev-time type check. JSDoc type annotations and `// @ts-check`
directives in core `.js` files are encouraged. TypeScript stays a
dev-only devDependency β€” emit-mode TypeScript, `.ts` / `.tsx` source,
JSX, frameworks, and bundlers remain forbidden in core.
- New UI state lives in `localStorage` (or a backend endpoint), not in a
framework store.
- Naming: camelCase JS, kebab-case CSS, snake_case plugin IDs. Player
Expand Down Expand Up @@ -117,6 +127,12 @@ format. Both must keep playing across releases.
- Existing arrangement IDs, sloppak manifests, and the highway
WebSocket message shape are stable contracts. Breaking changes
require a CHANGELOG entry under "Migration notes".
- The plugin-facing JS contracts β€” `window.slopsmith`, `window.highway`,
the highway WebSocket message shape, and the `setRenderer`
visualization contract β€” are declared in `static/slopsmith.d.ts` and
form part of this stable-contract surface. The `.d.ts` is the typed
source of truth; a breaking change to it requires the same CHANGELOG
entry under "Migration notes".

### V. Pure-Function Core Libraries, Tested

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Perf**: reduce per-frame allocations in the 2D highway chord + lyric render paths. `_ensureChordRenderCache` now also caches `sortedNotes` / `nonZeroNotes` / `nonZeroFrets` / `allMuted` / `hasMultipleNotes` (computed once per chord, invalidated on `src` / `_inverted` / `chordTemplates` change β€” the third key catches a stale `isOpen`-derived classification when the WS `chord_templates` message lands after the final `chords` chunk), so `drawChords` no longer re-sorts / re-filters / spreads min-max per visible chord per frame. The in-chord unison bend classification is folded inline (no `chordPositions.filter` Γ— 2 per frame). `drawLyrics` memoizes `ctx.measureText` results in a two-level `Map<fontSize, Map<text, width>>` so cache hits don't allocate a composite string key. Lit-sustain shimmer in `drawSustains` swaps the 4 per-note-per-frame `Math.random()` calls for a 64-entry precomputed jitter LUT (xorshift32-seeded β€” the LUT contents are reload-stable and test-reproducible; rendered shimmer is deterministic per `createHighway()` instance, since the seed includes that instance's `_frameIdx`) indexed by `(frameIdx + n.s + ⌊n.tΒ·60βŒ‹)`, visually indistinguishable and allocation-free.

### Added
- Optional JS type checking via `tsc --noEmit`. Added a `static/slopsmith.d.ts` ambient declaration file β€” the typed source of truth for the plugin-facing contract (`window.slopsmith`, `window.highway`, the highway WebSocket message shape, the `setRenderer` visualization contract, the keyboard-shortcut API) β€” plus a `tsconfig.json`, a `typecheck` npm script, and a CI step. TypeScript is a dev-only devDependency: no build/emit step, no runtime dependency, every shipped `.js` file stays byte-identical. Core `.js` files opt in individually with a `// @ts-check` directive (currently `static/app.js` and `static/highway.js`); files without it are unaffected.
- **Player progression: Mastery Rank, instrument-path challenges, daily/weekly quests, Decibels currency, cosmetics shop (spec 010).** Onboarding gains two steps: pick one or more **instrument paths** (Guitar / Bass / Drums β€” data-driven, more can ship as content) and a **calibration challenge** offer (play the bundled Slopsmith Diagnostic with note detection at 100% accuracy β€” or skip; either way you reach **Mastery Rank 1**, and a skipped calibration can still be completed later from the Progress screen). Each path levels by completing a content-defined number of **challenges** (any order) from that level's set; Mastery Rank = onboarding rank + the sum of path levels, starting at 0 on a fresh install. The existing unified XP backend is untouched but the frontend renames it to **Decibels (dB)** β€” a spendable currency earned ONLY by playing (songs, FeedBarcade rounds, quest rewards; no real-money path exists or may be added) β€” with spend tracked in a separate wallet so lifetime earnings stay monotonic. Rotating **daily/weekly quests** (deterministic per period, lazy instantiation, local-midnight / Monday resets) award dB and feed `quest_completed` challenges. A new **Progress** screen (rank hero, per-path checklists, quest countdowns, add-a-path) and **Shop** screen (themes via CSS-variable swaps under `html[data-fb-theme]`, avatar frames; atomic balance-checked purchases β€” 402 on insufficient dB, 409 on re-buy) join the v3 nav, and the topbar badge now shows Rank + challenge-set progress + dB balance. All definitions live in `data/progression/` JSON (paths/levels/challenges, quest pools, shop catalog) β€” adding a rank, challenge, quest, or cosmetic is a content edit + restart, never code; invalid content degrades to logged warnings. New tables (additive + idempotent): `progression_state`, `player_paths`, `challenge_progress`, `quest_state`, `wallet`, `shop_owned`, `shop_equipped`. New endpoints: `GET /api/progression`, `POST /api/progression/paths|onboarding|events` (events whitelists `minigame_run`; `song_completed` stays server-derived inside `POST /api/stats`, which now resolves the instrument server-side and reports an additive `progression` outcome key), `GET /api/shop`, `POST /api/shop/buy|equip`; equipped cosmetics ride along on `GET /api/profile`. A new **`progression` capability domain** (core-owned, kind: command, safety: safe β€” `inspect`, `record-event`, `list-shop`, `buy-item`/`equip-item` gated on user action) emits `challenge-completed`/`quest-completed`/`path-level-up`/`rank-changed`/`db-changed`/`calibration-completed`/`cosmetic-equipped`, mirrored as `progression:*` window events, with a redaction-safe diagnostics contributor; backend plugins get the symmetric `record_progression_event` context hook (the bundled minigames hub reports runs through it, guarded for standalone). Spec: `specs/010-progression-domain/`. Tests: `tests/test_progression.py`, `tests/test_progression_api.py`. **Migration notes:** existing XP totals carry over as lifetime dB (balance = lifetime βˆ’ spent); resetting a per-source XP contribution (e.g. a minigames profile reset) after spending can clamp the spendable balance to 0 until new dB is earned; drums-path v1 content uses currently-satisfiable goals (arcade rounds, quests, any-instrument plays) until drums scoring lands.
- **3D highway score FX (notedetect game-scoring layer).** The bundled `highway_3d` renderer now visualizes the scoring layer shipped in slopsmith-plugin-notedetect β‰₯1.13: floating **"+N" score pops** above each judged gem (sourced from the note-state provider's new `{ points, mult, popKey }` verdict fields β€” chord members share the chord-level `popKey`, so a chord pops once, not once per string), plus session-level FX from the new `notedetect:fx` event β€” a particle burst at the strike line on streak milestones (25/50/every 100), an expanding ring pulse on multiplier tier-ups (Γ—2/Γ—3/Γ—4), and a brief red wash when a β‰₯10 streak breaks. Colors and the pop font follow the user's notedetect scoring-UI skin (`slopsmith_notedetect_skin`: neon/esports/metal, refreshed live on the `notedetect:skin` bus event). Everything renders on the existing 2D overlay canvas from fixed-size slot pools β€” no Three.js geometry, no text-sprite cache traffic, near-zero cost when idle β€” and degrades to a silent no-op with older notedetect builds (the new fields/events simply never arrive). Splitscreen panels scope FX to their own detector instance via the bubbling per-panel `notedetect:fx` dispatch. `plugins/highway_3d` v3.24.1.
- **3D highway: slide direction arrows + gem-follow animation.** Slide notes now show a β€Ί/β€Ή arrow indicating which way the slide goes β€” on the note/gem itself, as an early preview on the neck before the note arrives, and (optionally) chained further ahead for multi-leg slides β€” each independently toggleable in Settings β†’ 3D Highway (`slideArrowApproachVisible`, `slideArrowNeckVisible`, `slideArrowChainPreviewVisible`). The note gem also now visually glides from its starting fret to the slide's destination over the note's sustain and holds there through the brief post-sustain linger, instead of snapping back to the starting fret β€” most noticeable on unpitched "slide to nothing" notes. `plugins/highway_3d` v3.25.2.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ Sloppak is the preferred format for new features. The [Stems plugin](https://git
- **Styling** β€” Tailwind CSS utility classes, dark theme (`bg-dark-600`, `text-gray-300`, accent `#4080e0`, gold `#e8c040`). Tailwind is served as a **prebuilt** stylesheet (`static/tailwind.min.css`, regenerated by `bash scripts/build-tailwind.sh`), **never** the runtime Play CDN β€” the CDN's on-the-fly JIT rescanned the DOM on the main thread and dropped ~26% of frames with the 3D highway (slopsmith-desktop#110). The committed CSS only contains classes the build scanner saw, so CI (`tailwind-fresh`) rebuilds and diffs it; run the build script and commit when you add new classes. A plugin that uses classes not guaranteed in core (notably arbitrary values like `w-[37px]`) MUST ship its own compiled stylesheet via the `styles` manifest key, built with `corePlugins.preflight = false` (utilities only β€” core ships the one base reset). Plugins MUST NOT load the Tailwind Play CDN or any runtime CSS JIT. See constitution Principle II.
- **Naming** β€” camelCase for JS functions, kebab-case for CSS classes, snake_case for plugin IDs
- **Player layout** β€” `#player` is `display:flex; flex-direction:column; position:fixed; inset:0`. `#highway` is `flex:1`. `#player-controls` sits at the bottom. Hiding the highway collapses the layout β€” use `margin-top: auto` on controls if you need to hide it.
- **Plugin contract types** β€” the plugin-facing JS surface (`window.slopsmith`, `window.highway`, the highway WebSocket message shape, the `setRenderer` contract, the keyboard-shortcut API) is declared in `static/slopsmith.d.ts`. That file is the typed source of truth β€” consult it for exact signatures, and update it when you change a contract. Core `.js` files opt into checking with a `// @ts-check` directive on line 1; `npm run typecheck` (`tsc --noEmit`, dev/CI-only) verifies them. No build or emit step β€” the shipped files are plain JS.

## Backend Conventions

Expand Down
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
"test:headed": "playwright test --headed",
"test:debug": "playwright test --debug",
"test:js": "node --test tests/js/*.test.js 'tests/plugins/*/js/*.test.js'",
"install:playwright": "playwright install chromium"
"install:playwright": "playwright install chromium",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@playwright/test": "^1.59.1"
"@playwright/test": "^1.59.1",
"typescript": "^5.4"
}
}
}
Loading
Loading