跳到主要内容

2026-03-16 Chart Catalog And Levels Schema Update

This note records the 2026-03-16 change that simplified the backend levels table and the client bootstrap chart-directory flow.

Why this changed

The previous redesign pushed too much manifest-level data into MySQL and into the authenticated bootstrap response. That turned the levels table into a partial copy of local chart files.

That is not necessary for the current architecture:

  • chart files and manifest.yml are still delivered through the resource sync pipeline
  • the backend only needs enough metadata to know which charts are enabled
  • the client only needs enough metadata to decide whether a local song or difficulty file should be deserialized

Current model

The backend levels table is now a lightweight chart catalog. One row represents one playable chart.

Recommended core columns:

  • id: RawLevel.meta.levelId
  • name: chart display name
  • difficulty_constant: chart constant
  • version: chart or song version string
  • created_at: when the chart row was created in the server catalog
  • is_active: whether this chart should be exposed to clients

Fields that already exist in local chart files are intentionally not duplicated here:

  • icon
  • composer
  • unlock payloads
  • comments
  • tags
  • resource-pack hashes
  • other manifest-only presentation data

Server endpoint

The authenticated bootstrap endpoint is now:

GET /server/charts
Authorization: Bearer <serverAccessToken>

Example response:

[
{
"levelId": 100091,
"name": "Song Title",
"difficultyConstant": 10.5,
"version": "1.0",
"createdAt": "2026-03-16T20:30:00"
}
]

The endpoint only returns active charts.

Client flow

NetworkManager.initialize() now runs these bootstrap tasks in parallel after /server/login succeeds:

  • resource integrity verification
  • GET /server/collections
  • remote resource-pack manifest loading
  • GET /server/charts

The response is stored as ActiveChartCatalog.

RawSongDeserializer then applies two checks:

  1. Each local difficulty file is checked by meta.uid.
  2. Only files whose levelId exists in the remote chart catalog are deserialized.

If the charts request fails or returns empty, the client falls back to the old behavior and loads every local chart under Data/Charts.

SQL scripts

Backend SQL artifacts now align with this lighter model:

  • src/main/resources/db/schema.sql
    • new environments use the minimal levels schema
  • src/main/resources/db/migrate_levels_to_catalog.sql
    • upgrades the legacy levels table into the lightweight chart catalog
  • src/main/resources/db/migrate_legacy_session_ids.sql
    • converts legacy numeric session foreign keys in gameplay_records into string session ids

Compatibility note

RecordService.ensureLevelExists(...) still auto-creates a placeholder row if a record upload references an unknown level_id.

Those rows are intentionally minimal:

  • name = "Level <id>"
  • version = unknown
  • is_active = false

They exist only to keep uploads from failing before the real chart catalog is imported.