MajRadar 是一个独立的的Simai谱面雷达分析和拟合定数估计组件。通过分析SimaiChart格式的已解析谱面计算固定七维特征产生雷达轴和拟合定数。
推荐直接复制 Samples~/MajdataPlay 中的静态薄调用层。
ChartRadarSnapshot snapshot = await ChartRadarService.AnalyzeAsync(
simaiChart,
cancellationToken);静态 facade 只持有无单谱状态的 Runtime;显示当前谱面的组件仍负责 token、selection
generation 和缓存。sample 返回 ChartRadarSnapshot
固定维度可以通过 enum 直接读取,同时保留原有字符串字典接口:
double? noteScore = snapshot.GetScore(RadarOutputDimension.Note);
double? trickyRaw = snapshot.GetRawValue(RadarOutputDimension.SlideTricky);
// 动态 UI 和现有代码仍可直接使用字符串 key。
double? sameNoteScore = snapshot.Scores["note"];RadarOutputDimension 只包含公开的六个雷达轴和 FittedConstant,不包含仅用于拟合的
slide_cumulate。拟合定数仍优先从 snapshot.FittedConstant 读取。
已有 MajSimai 解析结果时,优先复用现成的谱面:
RadarResult result = runtime.Analyze(existingSimaiChart, cancellationToken);需要保持 Unity 主线程响应时,使用异步包装:
RadarResult result = await _runtime.AnalyzeAsync(
existingSimaiChart,
cancellationToken);只有一段 inote 文本时,可以使用:
RadarResult result = await _runtime.ParseAndAnalyzeAsync(
inote,
cancellationToken);扩展Slidecode的长度算法需要依赖Play自身的Parser。
internal sealed class PlayExtendedSlideBarCountProvider
: IExtendedSlideBarCountProvider
{
public int ResolveBarCount(string slideCode)
{
var path = SlideCodeParser.Parse(slideCode);
return SlideDataBuilder.BuildArrowData(path).Length - 2;
}
}provider只接受SlideCode,例如 1P6K7,并且必须返回正数 arrow/bar count。
provider 抛出的异常或非正结果会变成结构化适配失败。未提供 provider 时,普通谱面仍可
正常分析;遇到 K Slide 会返回错误
取消采用合作式机制,并作为结果数据返回:
if (result.IsCancelled)
return;AnalyzeAsync会在下一个检查点返回Status == "cancelled"的RadarResult。- MajSimai 没有提供解析中途取消,因此
ParseAndAnalyzeAsync只能parser 返回后立即再次检查。 - 同步provider应调用中途不会被取消。
应为当前选歌持有一个 CancellationTokenSource。歌曲或难度变化时,取消上一份
token、启动新分析,并丢弃过期的结果。
if (result.IsSuccess)
{
foreach (var dimension in result.DimensionOrder)
Render(dimension, result.Scores[dimension]);
}
else if (!result.IsCancelled)
{
LogErrors(result.Errors);
}Analysis.Features包含模型使用的固定七维,包括内部维度slide_cumulate。RawValues和Scores始终保持公开形状:六个雷达轴加fitted_constant;不可用项为null。- 只有七维 raw 全部成功时才生成
FittedConstant和映射后的 score。 - 拟合输出在模块内部限位;上下限由
RegressionBetaModel的MinimumFittedConstant/MaximumFittedConstant常量配置,当前为 0~18。 partial会保留已完成的特征,但不会生成拟合定数。
dotnet test MajRadar.slnx
dotnet pack MajRadar.csproj -c ReleaseNuGet包以 netstandard2.1 为目标,包含MajSimai包依赖。
MajRadar is a Unity-free radar analyser and fitted-constant estimator for Simai
charts. It consumes MajSimai's typed SimaiChart, preserves chart-relative
timing, computes seven fixed raw features, applies the frozen regression model,
and maps the public radar axes.
MajdataPlay should copy the static integration facade from
Samples~/MajdataPlay. The sample wires the single
PlayExtendedSlideBarCountProvider directly into one long-lived RadarRuntime;
callers do not hold a service instance and do not need factories, options, or
per-request dependency selection:
ChartRadarSnapshot snapshot = await ChartRadarService.AnalyzeAsync(
simaiChart,
cancellationToken);The static facade retains only the Runtime, which has no per-chart state. The component that owns the current chart still owns its token, selection generation, and cache. The sample returns a lightweight snapshot so UI caches do not retain the full adapted event list and seven-dimension analysis graph.
Known dimensions can be read through the enum while the existing string-keyed dictionaries remain available:
double? noteScore = snapshot.GetScore(RadarOutputDimension.Note);
double? trickyRaw = snapshot.GetRawValue(RadarOutputDimension.SlideTricky);
// Existing and dynamic UI code can keep using literal keys.
double? sameNoteScore = snapshot.Scores["note"];RadarOutputDimension contains the six public radar axes and
FittedConstant; it intentionally excludes the internal regression-only
slide_cumulate. Prefer snapshot.FittedConstant when reading the fitted value.
Reuse an existing chart parsed by MajSimai whenever possible:
RadarResult result = runtime.Analyze(existingSimaiChart, cancellationToken);Use the asynchronous wrapper when the caller must keep the Unity main thread responsive:
RadarResult result = await _runtime.AnalyzeAsync(
existingSimaiChart,
cancellationToken);Standalone callers that only have one inote string may use:
RadarResult result = await _runtime.ParseAndAnalyzeAsync(
inote,
cancellationToken);ParseAndAnalyzeAsync is a convenience boundary, not a song loader. The caller
continues to own files, song metadata, chart type, artwork, and audio offsets.
Extended K Slides need gameplay geometry supplied by the host. MajRadar keeps
the dependency narrow and does not reference MajdataPlay:
internal sealed class PlayExtendedSlideBarCountProvider
: IExtendedSlideBarCountProvider
{
public int ResolveBarCount(string slideCode)
{
var path = SlideCodeParser.Parse(slideCode);
return SlideDataBuilder.BuildArrowData(path).Length - 2;
}
}The provider receives a normalized SlideCode such as 1P6K7. It must return a
positive arrow/bar count. Provider exceptions and non-positive results become a
structured adaptation failure. Without a provider, ordinary charts still work,
while a chart containing a K Slide returns an error instead of guessing.
Ordinary Slides, feature parameters, regression coefficients, and score mapping are built in and are not dependency-injected.
Cancellation is cooperative and returned as data:
if (result.IsCancelled)
return;Analyzechecks the token during adaptation, between feature dimensions, and throughout the high-complexity Sweep candidate, family, hand-motion, and selection loops.- Other dimensions check at their feature or bounded section boundaries.
AnalyzeAsyncdoes not forcibly abort its worker thread; it returns aRadarResultwithStatus == "cancelled"after the next observation point.- MajSimai does not expose cancellation for an in-progress parse. Therefore
ParseAndAnalyzeAsyncchecks immediately before parsing and again when the parser returns. - The synchronous provider method is expected to be short and bounded; it is not interrupted in the middle of a call.
The host owns one CancellationTokenSource per current selection. On a song or
difficulty change, cancel the previous source, start a new analysis, and discard
any result whose selection generation is no longer current. Cancellation alone
does not replace the generation check because a completed older task may race a
new selection.
if (result.IsSuccess)
{
foreach (var dimension in result.DimensionOrder)
Render(dimension, result.Scores[dimension]);
}
else if (!result.IsCancelled)
{
LogErrors(result.Errors);
}Analysis.Featurescontains all seven fixed model inputs, including internalslide_cumulate.RawValuesandScoresalways use the shape-stable public order: six radar axes plusfitted_constant; unavailable entries arenull.FittedConstantand mapped scores are produced only when all seven raw features succeed.- Predictions are clamped inside the module using
MinimumFittedConstantandMaximumFittedConstantinRegressionBetaModel(currently 0 and 18). partialpreserves completed feature results but does not produce a fitted constant.fitted_constantis identity-mapped and is not on the radar axes' 0-250 scale.
MajdataPlay should pin MajRadar as a Git submodule beside its existing MajSimai
submodule. Unity compiles Runtime/ through MajRadar.asmdef, which references
the single existing MajSimai assembly. Do not install the MajRadar NuGet package
into the same Unity project.
MajRadar is code-only and ignores Unity-generated *.meta files inside the
submodule. The Play repository should still commit the
Assets/Plugins/MajRadar.meta file for the submodule directory itself. Revisit
this policy if the package later adds prefabs, ScriptableObjects, or other assets
that require stable GUIDs.
dotnet test MajRadar.slnx
dotnet pack MajRadar.csproj -c ReleaseThe NuGet package targets netstandard2.1 and declares one MajSimai package
dependency. Pull requests build and test; main-branch pushes publish unique CI
prereleases, while v* tags publish stable versions.
Compatibility tests can compile against a source checkout matching Play's pin:
dotnet test MajRadar.slnx \
-p:MajSimaiProject=/absolute/path/to/MajSimai.csprojSong discovery, metadata, audio offsets, visualizers, training, and experimental pipelines intentionally remain outside this runtime component.