A .NET 8 service that calculates osu! performance points and difficulty attributes through the official osu!lazer ruleset packages. Its HTTP contract matches the external calculator interface used by perfcho.py.
- Pins
ppy.osu.Gameand the Osu, Taiko, Catch, and Mania ruleset packages to2026.702.1. - Supports
osu,taiko,fruits, andmania, including valid cross-ruleset beatmap conversion. - Supports the
vanilla,relax, and osu!standardautopilotvariants. - Supports lazer mod JSON and mod settings. Unknown mods, unsupported settings, out-of-range values, and incompatible combinations return
422. - Supports
lazerandclassicthroughrelease_configuration.score_system. Classic scores use the official stable-to-standardised score migration path.
This project does not copy or reimplement the PP formulas. Star ratings and PP are produced by the pinned official DifficultyCalculator and PerformanceCalculator implementations.
POST /v1/performance/calculate
Content-Type: multipart/form-dataThe request must contain exactly one file part named metadata, with filename metadata.json and content type application/json. The metadata and response schemas follow perfcho.py/.agent-space/docs/performance-calculation.md.
Operational endpoints:
GET /healthzGET /v1/capabilities
Raw beatmaps are cached by SHA-256 in the shared process memory cache. Disk caching is disabled by default; when enabled, entries are stored under CACHE_DIRECTORY/beatmaps. Every downloaded object is verified against beatmap_sha256 before it is decoded or persisted.
The difficulty cache identity includes:
beatmap SHA-256
osu! package version and DifficultyCalculator.Version
difficulty formula, release, and version
target ruleset and variant
resolved mod acronyms and all non-default settings
Difficulty attributes are stored in process memory and optionally in Redis. Concurrent misses for the same key are coalesced into one calculation per process. CPU work is bounded by a SemaphoreSlim; the default concurrency is the logical processor count, and saturation returns 429 when no queue timeout is configured.
The calculator does not cache authoritative score or user PP results. perfcho.py remains responsible for persisting PP by score and release.
Application settings use explicit uppercase environment variables with single underscores. ASP.NET Core framework variables such as ASPNETCORE_URLS keep their standard names. Automatic section-style environment binding is disabled for application settings.
| Environment variable | Description |
|---|---|
CALCULATOR_CODE |
Calculator identity; defaults to perfcho-pp |
FORMULA_CODE |
Performance formula code; defaults to official |
RELEASE_VERSION |
Performance release version; defaults to 2026.07.1 |
DIFFICULTY_FORMULA_CODE |
Difficulty formula code; defaults to official-difficulty |
DIFFICULTY_RELEASE_VERSION |
Difficulty release version; defaults to 2026.07.1 |
MAXIMUM_CONCURRENT_CALCULATIONS |
CPU calculation limit; 0 uses the logical processor count |
CALCULATION_QUEUE_TIMEOUT_MILLISECONDS |
Time to wait for calculation capacity; 0 returns 429 immediately |
CACHE_DIRECTORY |
Content-addressed beatmap cache directory |
CACHE_DISK_ENABLED |
Persist raw beatmaps on disk; defaults to false |
CACHE_MAXIMUM_MEMORY_CACHE_BYTES |
Shared process memory cache size for beatmaps and difficulty attributes; defaults to 128 MiB |
CACHE_MAXIMUM_BEATMAP_BYTES |
Maximum size of one beatmap |
CACHE_MAXIMUM_DISK_BYTES |
Disk cache limit; LRU entries are removed to 90% after the limit is crossed |
CACHE_MAXIMUM_CONCURRENT_DOWNLOADS |
Maximum concurrent beatmap downloads |
BEATMAP_DOWNLOAD_TIMEOUT_SECONDS |
Timeout covering beatmap headers and response body |
DIFFICULTY_CACHE_TTL_HOURS |
Difficulty cache lifetime |
REDIS_CONNECTION_STRING |
Optional Redis connection string |
REDIS_INSTANCE_NAME |
Redis key prefix |
Release identity is validated by calculator, formula, and release version. Release configuration remains part of the calculation input and cache identity.
The service does not follow redirects from beatmap_url. Production deployments should expose the calculator only on an internal network accessible by perfcho workers.
dotnet restore Perfcho.Performance.sln --locked-mode
dotnet test Perfcho.Performance.sln --configuration Release --no-restore
dotnet run --project src/Perfcho.PerformanceThe Development launch profile listens on http://127.0.0.1:6001.
Run with Redis:
REDIS_CONNECTION_STRING=127.0.0.1:6379 \
dotnet run --project src/Perfcho.Performancedocker build -t perfcho-pp .
docker run --rm -p 6001:6001 perfcho-pp