@world-engines/authoring-ui 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +46 -0
- package/dist/DesktopAuthoringShell.d.ts +178 -0
- package/dist/DesktopAuthoringShell.js +235 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +12 -0
- package/dist/ladybug/BrowserGraphCanvasAdapter.d.ts +12 -0
- package/dist/ladybug/BrowserGraphCanvasAdapter.js +8 -0
- package/dist/ladybug/GraphCanvas.d.ts +121 -0
- package/dist/ladybug/GraphCanvas.js +1439 -0
- package/dist/ladybug/GraphEdge.d.ts +26 -0
- package/dist/ladybug/GraphEdge.js +41 -0
- package/dist/ladybug/LadybugExplorer.d.ts +58 -0
- package/dist/ladybug/LadybugExplorer.js +83 -0
- package/dist/ladybug/LadybugSurfaces.d.ts +25 -0
- package/dist/ladybug/LadybugSurfaces.js +9 -0
- package/dist/ladybug/LadybugTable.d.ts +15 -0
- package/dist/ladybug/LadybugTable.js +26 -0
- package/dist/ladybug/entity-props.d.ts +98 -0
- package/dist/ladybug/entity-props.js +363 -0
- package/dist/ladybug/graph-edge-geometry.d.ts +19 -0
- package/dist/ladybug/graph-edge-geometry.js +78 -0
- package/dist/ladybug/graph-edge-mask.d.ts +21 -0
- package/dist/ladybug/graph-edge-mask.js +52 -0
- package/dist/ladybug/graph-groups.d.ts +47 -0
- package/dist/ladybug/graph-groups.js +245 -0
- package/dist/ladybug/graph-layout-contract.d.ts +34 -0
- package/dist/ladybug/graph-layout-contract.js +55 -0
- package/dist/ladybug/graph-layout-solver.d.ts +16 -0
- package/dist/ladybug/graph-layout-solver.js +96 -0
- package/dist/ladybug/graph-layout-wasm.d.ts +9 -0
- package/dist/ladybug/graph-layout-wasm.js +88 -0
- package/dist/ladybug/graph-layout-worker-protocol.d.ts +18 -0
- package/dist/ladybug/graph-layout-worker-protocol.js +1 -0
- package/dist/ladybug/graph-layout-worker.d.ts +1 -0
- package/dist/ladybug/graph-layout-worker.js +66 -0
- package/dist/ladybug/graph-layout.d.ts +2 -0
- package/dist/ladybug/graph-layout.js +1 -0
- package/dist/ladybug/graph-layout.wasm +0 -0
- package/dist/ladybug/graph-node-metrics.d.ts +33 -0
- package/dist/ladybug/graph-node-metrics.js +173 -0
- package/dist/ladybug/graph-physics.d.ts +49 -0
- package/dist/ladybug/graph-physics.js +266 -0
- package/dist/ladybug/graph-placement.d.ts +33 -0
- package/dist/ladybug/graph-placement.js +253 -0
- package/dist/ladybug/graph.css +530 -0
- package/dist/ladybug/graph.d.ts +18 -0
- package/dist/ladybug/graph.js +9 -0
- package/dist/ladybug/score-tags.d.ts +11 -0
- package/dist/ladybug/score-tags.js +1 -0
- package/dist/map/MapWorkspace.d.ts +14 -0
- package/dist/map/MapWorkspace.js +9 -0
- package/dist/score/ScoreRadar.d.ts +10 -0
- package/dist/score/ScoreRadar.js +66 -0
- package/dist/score/index.d.ts +3 -0
- package/dist/score/index.js +3 -0
- package/dist/score/radar-index.d.ts +3 -0
- package/dist/score/radar-index.js +2 -0
- package/dist/score/scenario-scoring-features.d.ts +90 -0
- package/dist/score/scenario-scoring-features.js +380 -0
- package/dist/score/scenario-structural-score.d.ts +76 -0
- package/dist/score/scenario-structural-score.js +211 -0
- package/dist/score/score-presentation.d.ts +51 -0
- package/dist/score/score-presentation.js +44 -0
- package/dist/score/score-radar.css +1 -0
- package/dist/sync/DownloadPanel.d.ts +42 -0
- package/dist/sync/DownloadPanel.js +22 -0
- package/dist/sync/SyncSidebar.d.ts +47 -0
- package/dist/sync/SyncSidebar.js +31 -0
- package/dist/sync/UploadPanel.d.ts +64 -0
- package/dist/sync/UploadPanel.js +36 -0
- package/dist/trigger/TriggerBlueprint.d.ts +37 -0
- package/dist/trigger/TriggerBlueprint.js +26 -0
- package/dist/trigger/tmw/TmwBlueprint.d.ts +71 -0
- package/dist/trigger/tmw/TmwBlueprint.js +206 -0
- package/dist/trigger/tmw/TmwFlowLane.d.ts +22 -0
- package/dist/trigger/tmw/TmwFlowLane.js +44 -0
- package/dist/trigger/tmw/TmwSourceLane.d.ts +17 -0
- package/dist/trigger/tmw/TmwSourceLane.js +38 -0
- package/dist/trigger/tmw/TmwTabs.d.ts +20 -0
- package/dist/trigger/tmw/TmwTabs.js +8 -0
- package/dist/trigger/tmw/TmwTraceLane.d.ts +9 -0
- package/dist/trigger/tmw/TmwTraceLane.js +6 -0
- package/dist/trigger/tmw/TriggerDebugTab.d.ts +9 -0
- package/dist/trigger/tmw/TriggerDebugTab.js +25 -0
- package/dist/trigger/tmw/translate.d.ts +3 -0
- package/dist/trigger/tmw/translate.js +2 -0
- package/dist/view/ViewWorkspace.d.ts +19 -0
- package/dist/view/ViewWorkspace.js +8 -0
- package/package.json +89 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import "./score-radar.css";
|
|
4
|
+
import { PRESENTATION_SCORE_AXES, STORY_SCORE_AXES, finiteScore, formatCompactScore, formatFullScore, scoreScaleValue, } from "./score-presentation.js";
|
|
5
|
+
const SERIES = [
|
|
6
|
+
{ key: "self", label: "本作品" },
|
|
7
|
+
{ key: "mean", label: "标签均值" },
|
|
8
|
+
{ key: "median", label: "标签中位数" },
|
|
9
|
+
];
|
|
10
|
+
function point(index, value, count) {
|
|
11
|
+
const angle = -Math.PI / 2 + (index * Math.PI * 2) / count;
|
|
12
|
+
const radius = 104 * (value / 100);
|
|
13
|
+
return [160 + Math.cos(angle) * radius, 160 + Math.sin(angle) * radius];
|
|
14
|
+
}
|
|
15
|
+
function pointText(position) {
|
|
16
|
+
return `${position[0].toFixed(2)} ${position[1].toFixed(2)}`;
|
|
17
|
+
}
|
|
18
|
+
function seriesPath(values) {
|
|
19
|
+
if (values.every((value) => value !== null)) {
|
|
20
|
+
return `${values.map((value, index) => `${index === 0 ? "M" : "L"} ${pointText(point(index, value, values.length))}`).join(" ")} Z`;
|
|
21
|
+
}
|
|
22
|
+
let connected = false;
|
|
23
|
+
return values.flatMap((value, index) => {
|
|
24
|
+
if (value === null) {
|
|
25
|
+
connected = false;
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const command = connected ? "L" : "M";
|
|
29
|
+
connected = true;
|
|
30
|
+
return [`${command} ${pointText(point(index, value, values.length))}`];
|
|
31
|
+
}).join(" ");
|
|
32
|
+
}
|
|
33
|
+
function RadarCard({ title, axes, values, comparisonSelected, source, }) {
|
|
34
|
+
const titleId = useId();
|
|
35
|
+
const visibleSeries = SERIES.filter(({ key }) => key === "self" || (comparisonSelected && axes.some((axis) => finiteScore(values[axis.key]?.[key]) !== null)));
|
|
36
|
+
const outerPoints = axes.map((_, index) => point(index, 100, axes.length).join(",")).join(" ");
|
|
37
|
+
const hasSelfProfile = axes.some((axis) => finiteScore(values[axis.key]?.self) !== null);
|
|
38
|
+
return (_jsxs("article", { className: "score-radar__card", children: [_jsx("h3", { id: titleId, children: title }), _jsxs("svg", { className: "score-radar__plot", viewBox: "0 0 320 320", role: "img", "aria-labelledby": titleId, children: [[20, 40, 60, 80, 100].map((level) => (_jsx("polygon", { className: "score-radar__grid", points: axes.map((_, index) => point(index, level, axes.length).join(",")).join(" ") }, level))), axes.map((axis, index) => {
|
|
39
|
+
const end = point(index, 100, axes.length);
|
|
40
|
+
return _jsx("line", { className: "score-radar__spoke", x1: "160", y1: "160", x2: end[0], y2: end[1] }, axis.key);
|
|
41
|
+
}), _jsx("polygon", { className: "score-radar__boundary", points: outerPoints }), axes.map((axis, index) => {
|
|
42
|
+
const position = point(index, 127, axes.length);
|
|
43
|
+
const x = position[0];
|
|
44
|
+
const y = Math.min(305, Math.max(15, position[1]));
|
|
45
|
+
return (_jsx("text", { className: "score-radar__axis-label", "data-axis-label": axis.key, x: x, y: y, textAnchor: x < 145 ? "end" : x > 175 ? "start" : "middle", dominantBaseline: "middle", children: axis.label }, `label-${axis.key}`));
|
|
46
|
+
}), !hasSelfProfile && source === "reviewed" ? _jsx("text", { className: "score-radar__empty", x: "160", y: "160", textAnchor: "middle", children: "\u6682\u65E0\u5DF2\u5BA1\u6838\u753B\u50CF" }) : null, visibleSeries.map(({ key }) => {
|
|
47
|
+
const scaled = axes.map((axis) => scoreScaleValue(values[axis.key]?.[key]));
|
|
48
|
+
const path = seriesPath(scaled);
|
|
49
|
+
return path ? _jsx("path", { className: `score-radar__series score-radar__series--${key}`, "data-series": key, d: path }, key) : null;
|
|
50
|
+
}), visibleSeries.flatMap(({ key }) => axes.flatMap((axis, index) => {
|
|
51
|
+
const scaled = scoreScaleValue(values[axis.key]?.[key]);
|
|
52
|
+
if (scaled === null)
|
|
53
|
+
return [];
|
|
54
|
+
const position = point(index, scaled, axes.length);
|
|
55
|
+
return [_jsx("circle", { className: `score-radar__point score-radar__point--${key}`, cx: position[0], cy: position[1], r: "3" }, `${key}-${axis.key}`)];
|
|
56
|
+
}))] }), _jsx("ul", { className: "score-radar__values", "aria-label": `${title}各维度分数`, children: axes.map((axis) => (_jsxs("li", { children: [_jsx("span", { children: axis.label }), visibleSeries.map(({ key, label }) => {
|
|
57
|
+
const value = finiteScore(values[axis.key]?.[key]);
|
|
58
|
+
return _jsxs("span", { children: [_jsx("small", { children: label }), _jsx("strong", { title: formatFullScore(value), children: formatCompactScore(value) })] }, key);
|
|
59
|
+
})] }, axis.key))) })] }));
|
|
60
|
+
}
|
|
61
|
+
export function ScoreRadar({ story, presentation, comparisonSelected, source, className = "" }) {
|
|
62
|
+
const sourceLabel = source === "draft" ? "本作品数据来源:草稿预估" : "本作品数据来源:已审核结果";
|
|
63
|
+
const hasMean = comparisonSelected && [...STORY_SCORE_AXES, ...PRESENTATION_SCORE_AXES].some((axis) => finiteScore((axis.key in story ? story[axis.key] : presentation[axis.key])?.mean) !== null);
|
|
64
|
+
const hasMedian = comparisonSelected && [...STORY_SCORE_AXES, ...PRESENTATION_SCORE_AXES].some((axis) => finiteScore((axis.key in story ? story[axis.key] : presentation[axis.key])?.median) !== null);
|
|
65
|
+
return (_jsxs("section", { className: `score-radar ${className}`.trim(), "aria-label": "\u4F5C\u54C1\u8BC4\u5206\u96F7\u8FBE\u56FE", children: [_jsxs("header", { className: "score-radar__header", children: [_jsxs("div", { children: [_jsx("h2", { children: "\u4F5C\u54C1\u8868\u73B0" }), _jsxs("p", { children: [sourceLabel, "\uFF1B\u5BF9\u6BD4\u7EBF\u4EC5\u5C55\u793A\u6807\u7B7E\u771F\u5B9E\u7EDF\u8BA1\u6570\u636E\u3002"] })] }), _jsxs("ul", { className: "score-radar__legend", "aria-label": "\u56FE\u4F8B", children: [_jsx("li", { "data-series": "self", children: "\u672C\u4F5C\u54C1" }), hasMean ? _jsx("li", { "data-series": "mean", children: "\u6807\u7B7E\u5747\u503C" }) : null, hasMedian ? _jsx("li", { "data-series": "median", children: "\u6807\u7B7E\u4E2D\u4F4D\u6570" }) : null] })] }), _jsxs("div", { className: "score-radar__cards", children: [_jsx(RadarCard, { title: "\u6545\u4E8B\u7ED3\u6784", axes: STORY_SCORE_AXES, values: story, comparisonSelected: comparisonSelected, source: source }), _jsx(RadarCard, { title: "\u5448\u73B0\u8D28\u91CF", axes: PRESENTATION_SCORE_AXES, values: presentation, comparisonSelected: comparisonSelected, source: source })] })] }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { StructuralInput } from "./scenario-structural-score.js";
|
|
2
|
+
export type ArchetypeId = "solo_protagonist" | "world_codex" | "sparse_prototype" | "attribute_farm" | "ensemble_cast" | "relation_web" | "flat_graph" | "faction_conflict" | "location_atlas" | "item_catalog" | "event_driven" | "cg_gallery" | "voice_theater" | "puppet_show" | "text_heavyweight" | "ui_showcase" | "compact_tight" | "replay_hotspot" | "other";
|
|
3
|
+
export interface PerTypeCounts {
|
|
4
|
+
readonly character: number;
|
|
5
|
+
readonly setting: number;
|
|
6
|
+
readonly lore: number;
|
|
7
|
+
readonly faction: number;
|
|
8
|
+
readonly location: number;
|
|
9
|
+
readonly event: number;
|
|
10
|
+
readonly item: number;
|
|
11
|
+
readonly other: number;
|
|
12
|
+
}
|
|
13
|
+
export interface MediaBreakdown {
|
|
14
|
+
readonly cg_image: number;
|
|
15
|
+
readonly cg_video: number;
|
|
16
|
+
readonly audio: number;
|
|
17
|
+
readonly puppet: number;
|
|
18
|
+
readonly other: number;
|
|
19
|
+
}
|
|
20
|
+
export interface CharacterGraphStats {
|
|
21
|
+
readonly relation_count: number;
|
|
22
|
+
readonly max_degree: number;
|
|
23
|
+
readonly min_degree: number;
|
|
24
|
+
readonly median_degree: number;
|
|
25
|
+
readonly p90_degree: number;
|
|
26
|
+
readonly leaf_count: number;
|
|
27
|
+
readonly max_child_node_count: number;
|
|
28
|
+
readonly median_child_node_count: number;
|
|
29
|
+
readonly max_effective_attr: number;
|
|
30
|
+
readonly min_effective_attr: number;
|
|
31
|
+
readonly median_effective_attr: number;
|
|
32
|
+
readonly p90_effective_attr: number;
|
|
33
|
+
}
|
|
34
|
+
export interface ScoringFeatureAssets {
|
|
35
|
+
readonly description_chars?: number;
|
|
36
|
+
readonly cover_variant_count?: number;
|
|
37
|
+
readonly has_view_ref?: boolean;
|
|
38
|
+
readonly ui_html_variant_count?: number;
|
|
39
|
+
readonly media_binding_count?: number;
|
|
40
|
+
readonly media_breakdown?: Partial<MediaBreakdown>;
|
|
41
|
+
}
|
|
42
|
+
export interface ScoringFeatureDiagnostics {
|
|
43
|
+
readonly excluded_entity_count: number;
|
|
44
|
+
readonly invalid_entity_count: number;
|
|
45
|
+
readonly excluded_relation_count: number;
|
|
46
|
+
readonly dangling_relation_count: number;
|
|
47
|
+
readonly self_loop_relation_count: number;
|
|
48
|
+
readonly duplicate_relation_count: number;
|
|
49
|
+
readonly duplicate_media_binding_count: number;
|
|
50
|
+
}
|
|
51
|
+
export interface ScenarioScoringFeatures {
|
|
52
|
+
readonly entity_count: number;
|
|
53
|
+
readonly relation_count: number;
|
|
54
|
+
readonly total_prop_keys: number;
|
|
55
|
+
readonly lore_entity_count: number;
|
|
56
|
+
readonly description_chars: number;
|
|
57
|
+
readonly entity_props_text_chars: number;
|
|
58
|
+
readonly cover_variant_count: number;
|
|
59
|
+
readonly has_view_ref: boolean;
|
|
60
|
+
readonly ui_html_variant_count: number;
|
|
61
|
+
readonly media_binding_count: number;
|
|
62
|
+
readonly max_edge: number;
|
|
63
|
+
readonly min_edge: number;
|
|
64
|
+
readonly median_edge: number;
|
|
65
|
+
readonly p90_edge: number;
|
|
66
|
+
readonly max_attr: number;
|
|
67
|
+
readonly min_attr: number;
|
|
68
|
+
readonly median_attr: number;
|
|
69
|
+
readonly per_type_counts: PerTypeCounts;
|
|
70
|
+
readonly character_graph: CharacterGraphStats;
|
|
71
|
+
readonly media_breakdown: MediaBreakdown;
|
|
72
|
+
readonly diagnostics: ScoringFeatureDiagnostics;
|
|
73
|
+
}
|
|
74
|
+
export interface ArchetypeRuntimeSignals {
|
|
75
|
+
readonly avg_save?: number;
|
|
76
|
+
readonly max_round?: number;
|
|
77
|
+
readonly fav_count?: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 从实体/关系 wire 数据提取与 Rust 同口径的可信特征。
|
|
81
|
+
* duplicate entity id 会抛 `duplicate_entity_id:<id>`,调用方必须 fail closed。
|
|
82
|
+
*/
|
|
83
|
+
export declare function extractScenarioScoringFeatures(params: {
|
|
84
|
+
readonly entities: ReadonlyArray<unknown>;
|
|
85
|
+
readonly relations: ReadonlyArray<unknown>;
|
|
86
|
+
readonly assets?: ScoringFeatureAssets;
|
|
87
|
+
}): ScenarioScoringFeatures;
|
|
88
|
+
export declare const toStructuralInput: (features: ScenarioScoringFeatures) => StructuralInput;
|
|
89
|
+
export declare function detectAllMatchedArchetypes(input: ScenarioScoringFeatures, runtime?: ArchetypeRuntimeSignals): ArchetypeId[];
|
|
90
|
+
export declare function detectPrimaryArchetype(input: ScenarioScoringFeatures, runtime?: ArchetypeRuntimeSignals): ArchetypeId;
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
2
|
+
? value
|
|
3
|
+
: undefined;
|
|
4
|
+
const asString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
5
|
+
const normalizeToken = (raw) => raw.trim().toLowerCase().replace(/[- /]/g, "_");
|
|
6
|
+
const sourceOrControlKind = (kind) => new Set([
|
|
7
|
+
"source_document", "source_chunk", "source_control", "control", "control_node",
|
|
8
|
+
"graph_control", "scenario_control", "system_control"
|
|
9
|
+
]).has(kind)
|
|
10
|
+
|| kind.startsWith("world_source_")
|
|
11
|
+
|| kind.startsWith("source_")
|
|
12
|
+
|| kind.startsWith("control_")
|
|
13
|
+
|| kind.endsWith("_control");
|
|
14
|
+
const normalizedKind = (raw) => {
|
|
15
|
+
const kind = normalizeToken(raw);
|
|
16
|
+
if (sourceOrControlKind(kind))
|
|
17
|
+
return undefined;
|
|
18
|
+
if (["character", "npc", "person", "role", "actor", "world_document_person"].includes(kind)) {
|
|
19
|
+
return "character";
|
|
20
|
+
}
|
|
21
|
+
if (["setting", "world_setting", "worldview"].includes(kind))
|
|
22
|
+
return "setting";
|
|
23
|
+
if (["lore", "world_lore", "codex"].includes(kind))
|
|
24
|
+
return "lore";
|
|
25
|
+
if (["faction", "organization", "organisation", "group"].includes(kind))
|
|
26
|
+
return "faction";
|
|
27
|
+
if (["location", "place", "site", "scene", "world_document_place"].includes(kind)) {
|
|
28
|
+
return "location";
|
|
29
|
+
}
|
|
30
|
+
if (["item", "object", "artifact", "prop", "world_document_object"].includes(kind)) {
|
|
31
|
+
return "item";
|
|
32
|
+
}
|
|
33
|
+
if (["event", "incident", "world_document_event"].includes(kind))
|
|
34
|
+
return "event";
|
|
35
|
+
return "other";
|
|
36
|
+
};
|
|
37
|
+
const entityId = (entity) => asString(entity.entity_id) ?? asString(entity.id);
|
|
38
|
+
const relationEndpoint = (relation, keys) => {
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
const id = asString(relation[key]);
|
|
41
|
+
if (id !== undefined)
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
const boolMarker = (record, key) => {
|
|
47
|
+
if (record[key] === true)
|
|
48
|
+
return true;
|
|
49
|
+
return asRecord(record.props)?.[key] === true;
|
|
50
|
+
};
|
|
51
|
+
const technicalKey = (raw) => {
|
|
52
|
+
const key = normalizeToken(raw);
|
|
53
|
+
return new Set([
|
|
54
|
+
"raw_base64", "base64", "blob", "data_uri", "provenance", "source_ref", "source_refs",
|
|
55
|
+
"hash", "sha", "sha256", "digest", "checksum", "vector", "embedding", "embedding_hash",
|
|
56
|
+
"position", "needs_review", "created_at", "updated_at"
|
|
57
|
+
]).has(key)
|
|
58
|
+
|| ["_base64", "_hash", "_sha", "_sha256", "_digest", "_vector", "_embedding"]
|
|
59
|
+
.some((suffix) => key.endsWith(suffix));
|
|
60
|
+
};
|
|
61
|
+
const collectEffectiveLeaves = (value, path, output) => {
|
|
62
|
+
if (value === null || value === undefined)
|
|
63
|
+
return;
|
|
64
|
+
if (typeof value === "string") {
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
if (trimmed.length > 0)
|
|
67
|
+
output.push([path, trimmed, Array.from(trimmed).length]);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
71
|
+
output.push([path, String(value), 0]);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
value.forEach((item, index) => collectEffectiveLeaves(item, `${path}[${index}]`, output));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const record = asRecord(value);
|
|
79
|
+
if (record === undefined)
|
|
80
|
+
return;
|
|
81
|
+
if (Object.prototype.hasOwnProperty.call(record, "value")) {
|
|
82
|
+
collectEffectiveLeaves(record.value, path, output);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const key of Object.keys(record).sort()) {
|
|
86
|
+
if (technicalKey(key) || key === "trigger" || key === "states")
|
|
87
|
+
continue;
|
|
88
|
+
collectEffectiveLeaves(record[key], path.length === 0 ? key : `${path}.${key}`, output);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const effectiveAttrs = (entity) => {
|
|
92
|
+
const output = [];
|
|
93
|
+
const props = asRecord(entity.props);
|
|
94
|
+
if (props === undefined)
|
|
95
|
+
return output;
|
|
96
|
+
for (const key of Object.keys(props).sort()) {
|
|
97
|
+
if (technicalKey(key))
|
|
98
|
+
continue;
|
|
99
|
+
collectEffectiveLeaves(props[key], key, output);
|
|
100
|
+
}
|
|
101
|
+
return output;
|
|
102
|
+
};
|
|
103
|
+
const entityExcluded = (entity) => boolMarker(entity, "disabled")
|
|
104
|
+
|| boolMarker(entity, "control")
|
|
105
|
+
|| boolMarker(entity, "is_control")
|
|
106
|
+
|| boolMarker(entity, "scoring_excluded")
|
|
107
|
+
|| normalizedKind(typeof entity.kind === "string" ? entity.kind : "") === undefined;
|
|
108
|
+
const relationExcluded = (relation) => {
|
|
109
|
+
const kind = normalizeToken(typeof relation.kind === "string" ? relation.kind : "");
|
|
110
|
+
return sourceOrControlKind(kind)
|
|
111
|
+
|| kind.includes("source_chunk")
|
|
112
|
+
|| boolMarker(relation, "disabled")
|
|
113
|
+
|| boolMarker(relation, "control")
|
|
114
|
+
|| boolMarker(relation, "is_control")
|
|
115
|
+
|| boolMarker(relation, "scoring_excluded")
|
|
116
|
+
|| asRecord(relation.props)?.structural === false;
|
|
117
|
+
};
|
|
118
|
+
const relationSignature = (relation, from, to) => {
|
|
119
|
+
const kind = normalizeToken(typeof relation.kind === "string" ? relation.kind : "related_to");
|
|
120
|
+
const leaves = [];
|
|
121
|
+
for (const key of ["title", "content", "props", "fields"]) {
|
|
122
|
+
if (relation[key] !== undefined)
|
|
123
|
+
collectEffectiveLeaves(relation[key], key, leaves);
|
|
124
|
+
}
|
|
125
|
+
leaves.sort(([aPath, aValue], [bPath, bValue]) => aPath.localeCompare(bPath) || aValue.localeCompare(bValue));
|
|
126
|
+
return `${from}\u001f${to}\u001f${kind}\u001f${leaves.map(([p, v]) => `${p}=${v}`).join("\u001e")}`;
|
|
127
|
+
};
|
|
128
|
+
const distribution = (input) => {
|
|
129
|
+
if (input.length === 0)
|
|
130
|
+
return [0, 0, 0, 0];
|
|
131
|
+
const values = [...input].sort((a, b) => a - b);
|
|
132
|
+
const p90 = Math.min(values.length - 1, Math.max(0, Math.ceil(values.length * 0.9) - 1));
|
|
133
|
+
return [values[values.length - 1] ?? 0, values[0] ?? 0, values[Math.floor(values.length / 2)] ?? 0, values[p90] ?? 0];
|
|
134
|
+
};
|
|
135
|
+
const mediaBreakdown = (input) => ({
|
|
136
|
+
cg_image: input?.cg_image ?? 0,
|
|
137
|
+
cg_video: input?.cg_video ?? 0,
|
|
138
|
+
audio: input?.audio ?? 0,
|
|
139
|
+
puppet: input?.puppet ?? 0,
|
|
140
|
+
other: input?.other ?? 0
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* 从实体/关系 wire 数据提取与 Rust 同口径的可信特征。
|
|
144
|
+
* duplicate entity id 会抛 `duplicate_entity_id:<id>`,调用方必须 fail closed。
|
|
145
|
+
*/
|
|
146
|
+
export function extractScenarioScoringFeatures(params) {
|
|
147
|
+
const entities = params.entities.map(asRecord);
|
|
148
|
+
const relations = params.relations.map(asRecord);
|
|
149
|
+
const seenIds = new Set();
|
|
150
|
+
for (const entity of entities) {
|
|
151
|
+
if (entity === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
const id = entityId(entity);
|
|
154
|
+
if (id === undefined)
|
|
155
|
+
continue;
|
|
156
|
+
if (seenIds.has(id))
|
|
157
|
+
throw new Error(`duplicate_entity_id:${id}`);
|
|
158
|
+
seenIds.add(id);
|
|
159
|
+
}
|
|
160
|
+
const diagnostics = {
|
|
161
|
+
excluded_entity_count: 0,
|
|
162
|
+
invalid_entity_count: 0,
|
|
163
|
+
excluded_relation_count: 0,
|
|
164
|
+
dangling_relation_count: 0,
|
|
165
|
+
self_loop_relation_count: 0,
|
|
166
|
+
duplicate_relation_count: 0,
|
|
167
|
+
duplicate_media_binding_count: 0
|
|
168
|
+
};
|
|
169
|
+
const kinds = new Map();
|
|
170
|
+
const attrs = new Map();
|
|
171
|
+
const counts = {
|
|
172
|
+
character: 0, setting: 0, lore: 0, faction: 0, location: 0, event: 0, item: 0, other: 0
|
|
173
|
+
};
|
|
174
|
+
let entityPropsTextChars = 0;
|
|
175
|
+
let loreEntityCount = 0;
|
|
176
|
+
for (const entity of entities) {
|
|
177
|
+
if (entity === undefined) {
|
|
178
|
+
diagnostics.invalid_entity_count += 1;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const id = entityId(entity);
|
|
182
|
+
if (id === undefined) {
|
|
183
|
+
diagnostics.invalid_entity_count += 1;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (entityExcluded(entity)) {
|
|
187
|
+
diagnostics.excluded_entity_count += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const kind = normalizedKind(typeof entity.kind === "string" ? entity.kind : "") ?? "other";
|
|
191
|
+
const leaves = effectiveAttrs(entity);
|
|
192
|
+
attrs.set(id, leaves.length);
|
|
193
|
+
entityPropsTextChars += leaves.reduce((sum, [, , textChars]) => sum + textChars, 0);
|
|
194
|
+
kinds.set(id, kind);
|
|
195
|
+
counts[kind] += 1;
|
|
196
|
+
if (["setting", "lore", "faction", "location"].includes(kind))
|
|
197
|
+
loreEntityCount += 1;
|
|
198
|
+
}
|
|
199
|
+
const degree = new Map([...kinds.keys()].map((id) => [id, 0]));
|
|
200
|
+
const characterIds = new Set([...kinds].filter(([, kind]) => kind === "character").map(([id]) => id));
|
|
201
|
+
const characterNeighbors = new Map([...characterIds].map((id) => [id, new Set()]));
|
|
202
|
+
const seenRelations = new Set();
|
|
203
|
+
let relationCount = 0;
|
|
204
|
+
let characterRelationCount = 0;
|
|
205
|
+
for (const relation of relations) {
|
|
206
|
+
if (relation === undefined || relationExcluded(relation)) {
|
|
207
|
+
diagnostics.excluded_relation_count += 1;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const from = relationEndpoint(relation, ["from_id", "source_id", "from", "source"]);
|
|
211
|
+
const to = relationEndpoint(relation, ["to_id", "target_id", "to", "target"]);
|
|
212
|
+
if (from === undefined || to === undefined || !kinds.has(from) || !kinds.has(to)) {
|
|
213
|
+
diagnostics.dangling_relation_count += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (from === to) {
|
|
217
|
+
diagnostics.self_loop_relation_count += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const signature = relationSignature(relation, from, to);
|
|
221
|
+
if (seenRelations.has(signature)) {
|
|
222
|
+
diagnostics.duplicate_relation_count += 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
seenRelations.add(signature);
|
|
226
|
+
relationCount += 1;
|
|
227
|
+
degree.set(from, (degree.get(from) ?? 0) + 1);
|
|
228
|
+
degree.set(to, (degree.get(to) ?? 0) + 1);
|
|
229
|
+
const fromCharacter = characterIds.has(from);
|
|
230
|
+
const toCharacter = characterIds.has(to);
|
|
231
|
+
if (fromCharacter || toCharacter)
|
|
232
|
+
characterRelationCount += 1;
|
|
233
|
+
if (fromCharacter && !toCharacter)
|
|
234
|
+
characterNeighbors.get(from)?.add(to);
|
|
235
|
+
if (toCharacter && !fromCharacter)
|
|
236
|
+
characterNeighbors.get(to)?.add(from);
|
|
237
|
+
}
|
|
238
|
+
const [maxEdge, minEdge, medianEdge, p90Edge] = distribution([...degree.values()]);
|
|
239
|
+
const [maxAttr, minAttr, medianAttr] = distribution([...attrs.values()]);
|
|
240
|
+
const characterDegrees = [...characterIds].map((id) => degree.get(id) ?? 0);
|
|
241
|
+
const [characterMaxEdge, characterMinEdge, characterMedianEdge, characterP90Edge] = distribution(characterDegrees);
|
|
242
|
+
const [characterMaxAdjacent, , characterMedianAdjacent] = distribution([...characterNeighbors.values()].map((neighbors) => neighbors.size));
|
|
243
|
+
const [characterMaxAttr, characterMinAttr, characterMedianAttr, characterP90Attr] = distribution([...characterIds].map((id) => attrs.get(id) ?? 0));
|
|
244
|
+
const characterGraph = {
|
|
245
|
+
relation_count: characterRelationCount,
|
|
246
|
+
max_degree: characterMaxEdge,
|
|
247
|
+
min_degree: characterMinEdge,
|
|
248
|
+
median_degree: characterMedianEdge,
|
|
249
|
+
p90_degree: characterP90Edge,
|
|
250
|
+
leaf_count: characterDegrees.filter((value) => value <= 1).length,
|
|
251
|
+
max_child_node_count: characterMaxAdjacent,
|
|
252
|
+
median_child_node_count: characterMedianAdjacent,
|
|
253
|
+
max_effective_attr: characterMaxAttr,
|
|
254
|
+
min_effective_attr: characterMinAttr,
|
|
255
|
+
median_effective_attr: characterMedianAttr,
|
|
256
|
+
p90_effective_attr: characterP90Attr
|
|
257
|
+
};
|
|
258
|
+
const assets = params.assets;
|
|
259
|
+
return {
|
|
260
|
+
entity_count: kinds.size,
|
|
261
|
+
relation_count: relationCount,
|
|
262
|
+
total_prop_keys: [...attrs.values()].reduce((sum, value) => sum + value, 0),
|
|
263
|
+
lore_entity_count: loreEntityCount,
|
|
264
|
+
description_chars: assets?.description_chars ?? 0,
|
|
265
|
+
entity_props_text_chars: entityPropsTextChars,
|
|
266
|
+
cover_variant_count: assets?.cover_variant_count ?? 0,
|
|
267
|
+
has_view_ref: assets?.has_view_ref ?? false,
|
|
268
|
+
ui_html_variant_count: assets?.ui_html_variant_count ?? 0,
|
|
269
|
+
media_binding_count: assets?.media_binding_count ?? 0,
|
|
270
|
+
max_edge: maxEdge,
|
|
271
|
+
min_edge: minEdge,
|
|
272
|
+
median_edge: medianEdge,
|
|
273
|
+
p90_edge: p90Edge,
|
|
274
|
+
max_attr: maxAttr,
|
|
275
|
+
min_attr: minAttr,
|
|
276
|
+
median_attr: medianAttr,
|
|
277
|
+
per_type_counts: counts,
|
|
278
|
+
character_graph: characterGraph,
|
|
279
|
+
media_breakdown: mediaBreakdown(assets?.media_breakdown),
|
|
280
|
+
diagnostics
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
export const toStructuralInput = (features) => ({
|
|
284
|
+
entityCount: features.entity_count,
|
|
285
|
+
relationCount: features.relation_count,
|
|
286
|
+
totalPropKeys: features.total_prop_keys,
|
|
287
|
+
loreEntityCount: features.lore_entity_count,
|
|
288
|
+
descriptionChars: features.description_chars,
|
|
289
|
+
entityPropsTextChars: features.entity_props_text_chars,
|
|
290
|
+
coverVariantCount: features.cover_variant_count,
|
|
291
|
+
hasViewRef: features.has_view_ref,
|
|
292
|
+
uiHtmlVariantCount: features.ui_html_variant_count,
|
|
293
|
+
mediaBindingCount: features.media_binding_count
|
|
294
|
+
});
|
|
295
|
+
const graphEmpty = (graph) => Object.values(graph).every((value) => value === 0);
|
|
296
|
+
const matchesSoloProtagonist = (input) => {
|
|
297
|
+
const characters = input.per_type_counts.character;
|
|
298
|
+
const graph = input.character_graph;
|
|
299
|
+
const maxDegree = graph.max_degree > 0 ? graph.max_degree : input.max_edge;
|
|
300
|
+
const medianDegree = !graphEmpty(graph) ? graph.median_degree : input.median_edge;
|
|
301
|
+
const incidentRelations = graph.relation_count > 0 ? graph.relation_count : input.relation_count;
|
|
302
|
+
const supportingCastIsPeripheral = characters === 1
|
|
303
|
+
|| graph.leaf_count * 2 >= Math.max(0, characters - 1)
|
|
304
|
+
|| graph.max_child_node_count >= 2
|
|
305
|
+
|| graph.max_effective_attr >= graph.median_effective_attr + 4;
|
|
306
|
+
return characters >= 1
|
|
307
|
+
&& input.entity_count >= 3
|
|
308
|
+
&& input.relation_count >= 2
|
|
309
|
+
&& maxDegree >= 3
|
|
310
|
+
&& maxDegree / Math.max(medianDegree, 1) >= 3
|
|
311
|
+
&& maxDegree >= incidentRelations * 0.5
|
|
312
|
+
&& supportingCastIsPeripheral;
|
|
313
|
+
};
|
|
314
|
+
const matchesEnsembleCast = (input) => {
|
|
315
|
+
const graph = input.character_graph;
|
|
316
|
+
const maxDegree = graphEmpty(graph) ? input.max_edge : graph.max_degree;
|
|
317
|
+
const minDegree = graphEmpty(graph) ? input.min_edge : graph.min_degree;
|
|
318
|
+
const medianDegree = graphEmpty(graph) ? input.median_edge : graph.median_degree;
|
|
319
|
+
return input.per_type_counts.character >= 8
|
|
320
|
+
&& medianDegree >= 2
|
|
321
|
+
&& minDegree >= 1
|
|
322
|
+
&& maxDegree / Math.max(medianDegree, 1) < 2;
|
|
323
|
+
};
|
|
324
|
+
export function detectAllMatchedArchetypes(input, runtime = {}) {
|
|
325
|
+
const p = input.per_type_counts;
|
|
326
|
+
const media = input.media_breakdown;
|
|
327
|
+
const hits = [];
|
|
328
|
+
const add = (id, matched) => { if (matched)
|
|
329
|
+
hits.push(id); };
|
|
330
|
+
add("solo_protagonist", matchesSoloProtagonist(input));
|
|
331
|
+
add("world_codex", input.entity_count >= 10 && p.character <= 3
|
|
332
|
+
&& input.lore_entity_count / Math.max(input.entity_count, 1) >= 0.7
|
|
333
|
+
&& p.setting + p.lore >= 8);
|
|
334
|
+
add("sparse_prototype", input.entity_count < 8 && input.relation_count < 5
|
|
335
|
+
&& input.total_prop_keys < 20
|
|
336
|
+
&& input.description_chars + input.entity_props_text_chars < 500
|
|
337
|
+
&& input.media_binding_count === 0);
|
|
338
|
+
add("attribute_farm", input.entity_count >= 5 && input.median_attr >= 12 && input.min_attr >= 6
|
|
339
|
+
&& input.entity_props_text_chars / Math.max(input.total_prop_keys, 1) <= 8);
|
|
340
|
+
add("ensemble_cast", matchesEnsembleCast(input));
|
|
341
|
+
add("relation_web", input.entity_count >= 6
|
|
342
|
+
&& input.relation_count / Math.max(input.entity_count, 1) >= 3
|
|
343
|
+
&& input.median_edge >= 4 && input.min_edge >= 2);
|
|
344
|
+
add("flat_graph", input.entity_count >= 10
|
|
345
|
+
&& input.relation_count / Math.max(input.entity_count, 1) < 0.4
|
|
346
|
+
&& input.median_edge === 0 && input.max_edge <= 2);
|
|
347
|
+
add("faction_conflict", p.faction >= 3 && p.character >= p.faction
|
|
348
|
+
&& input.relation_count >= p.faction * p.faction / 2);
|
|
349
|
+
add("location_atlas", p.location >= 8
|
|
350
|
+
&& p.location / Math.max(input.entity_count, 1) >= 0.4 && input.median_edge >= 2);
|
|
351
|
+
add("item_catalog", p.item >= 10
|
|
352
|
+
&& p.item / Math.max(input.entity_count, 1) >= 0.4 && input.median_attr >= 6);
|
|
353
|
+
add("event_driven", p.event >= 6
|
|
354
|
+
&& p.event / Math.max(input.entity_count, 1) >= 0.3
|
|
355
|
+
&& input.relation_count / Math.max(p.event, 1) >= 1.5);
|
|
356
|
+
const cg = media.cg_image + media.cg_video;
|
|
357
|
+
add("cg_gallery", cg >= 20 && cg / Math.max(input.media_binding_count, 1) >= 0.7);
|
|
358
|
+
add("voice_theater", media.audio >= 15
|
|
359
|
+
&& media.audio / Math.max(input.media_binding_count, 1) >= 0.4 && p.character >= 2);
|
|
360
|
+
add("puppet_show", media.puppet >= 3
|
|
361
|
+
&& media.puppet / Math.max(input.media_binding_count, 1) >= 0.25 && p.character >= media.puppet);
|
|
362
|
+
add("text_heavyweight", input.description_chars + input.entity_props_text_chars >= 30_000
|
|
363
|
+
&& input.entity_props_text_chars / Math.max(input.total_prop_keys, 1) >= 60);
|
|
364
|
+
add("ui_showcase", input.has_view_ref && input.ui_html_variant_count >= 2
|
|
365
|
+
&& input.cover_variant_count >= 3);
|
|
366
|
+
add("compact_tight", input.entity_count >= 8 && input.entity_count <= 30
|
|
367
|
+
&& input.max_edge >= input.min_edge && input.max_edge - input.min_edge <= 3
|
|
368
|
+
&& input.max_attr >= input.min_attr && input.max_attr - input.min_attr <= 5
|
|
369
|
+
&& input.median_edge >= 1 && input.median_attr >= 3);
|
|
370
|
+
add("replay_hotspot", (runtime.avg_save ?? 0) >= 3
|
|
371
|
+
&& (runtime.max_round ?? 0) >= 100 && (runtime.fav_count ?? 0) >= 20);
|
|
372
|
+
return hits;
|
|
373
|
+
}
|
|
374
|
+
const PLAYER_ONLY = new Set(["replay_hotspot"]);
|
|
375
|
+
export function detectPrimaryArchetype(input, runtime = {}) {
|
|
376
|
+
const hits = detectAllMatchedArchetypes(input, runtime);
|
|
377
|
+
if (hits.includes("sparse_prototype"))
|
|
378
|
+
return "sparse_prototype";
|
|
379
|
+
return hits.find((id) => !PLAYER_ONLY.has(id)) ?? "other";
|
|
380
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type ArchetypeId, type ScenarioScoringFeatures } from "./scenario-scoring-features.js";
|
|
2
|
+
export declare const STRUCTURAL_WEIGHT_ENTITY_COUNT = 22;
|
|
3
|
+
export declare const STRUCTURAL_WEIGHT_RELATION_COUNT = 20;
|
|
4
|
+
export declare const STRUCTURAL_WEIGHT_ATTR_RICHNESS = 16;
|
|
5
|
+
export declare const STRUCTURAL_WEIGHT_LORE_COMPLETENESS = 16;
|
|
6
|
+
export declare const STRUCTURAL_WEIGHT_TEXT_VOLUME = 14;
|
|
7
|
+
export declare const STRUCTURAL_WEIGHT_ASSET_REFS = 12;
|
|
8
|
+
export declare const STRUCTURAL_SAT_K_ENTITY_COUNT = 60;
|
|
9
|
+
export declare const STRUCTURAL_SAT_K_RELATION_COUNT = 80;
|
|
10
|
+
export declare const STRUCTURAL_SAT_K_ATTR_RICHNESS = 6;
|
|
11
|
+
export declare const STRUCTURAL_SAT_K_LORE_COMPLETENESS = 8;
|
|
12
|
+
export declare const STRUCTURAL_SAT_K_TEXT_VOLUME = 4000;
|
|
13
|
+
export declare const STRUCTURAL_SAT_K_ASSET_REFS = 12;
|
|
14
|
+
export interface StructuralInput {
|
|
15
|
+
readonly entityCount: number;
|
|
16
|
+
readonly relationCount: number;
|
|
17
|
+
readonly totalPropKeys: number;
|
|
18
|
+
readonly loreEntityCount: number;
|
|
19
|
+
readonly descriptionChars: number;
|
|
20
|
+
readonly entityPropsTextChars: number;
|
|
21
|
+
readonly coverVariantCount: number;
|
|
22
|
+
readonly hasViewRef: boolean;
|
|
23
|
+
readonly uiHtmlVariantCount: number;
|
|
24
|
+
readonly mediaBindingCount: number;
|
|
25
|
+
}
|
|
26
|
+
export interface StructuralSourceInput {
|
|
27
|
+
readonly entities: ReadonlyArray<unknown>;
|
|
28
|
+
readonly relations: ReadonlyArray<unknown>;
|
|
29
|
+
readonly description: string;
|
|
30
|
+
readonly coverVariantCount: number;
|
|
31
|
+
readonly hasViewRef: boolean;
|
|
32
|
+
readonly uiHtmlVariantCount: number;
|
|
33
|
+
readonly mediaBindingCount: number;
|
|
34
|
+
}
|
|
35
|
+
export interface LocalStructuralProjectionInput {
|
|
36
|
+
readonly entities: ReadonlyArray<unknown>;
|
|
37
|
+
readonly relations: ReadonlyArray<unknown>;
|
|
38
|
+
readonly description: string;
|
|
39
|
+
readonly source_revision: number;
|
|
40
|
+
readonly source_digest: string;
|
|
41
|
+
}
|
|
42
|
+
export interface LocalStructuralProjection {
|
|
43
|
+
readonly source_revision: number;
|
|
44
|
+
readonly source_digest: string;
|
|
45
|
+
readonly features: ScenarioScoringFeatures;
|
|
46
|
+
readonly archetype: {
|
|
47
|
+
readonly primary: ArchetypeId;
|
|
48
|
+
readonly all_matched: readonly ArchetypeId[];
|
|
49
|
+
};
|
|
50
|
+
readonly structural: {
|
|
51
|
+
readonly score: number;
|
|
52
|
+
readonly dimensions: StructuralDims;
|
|
53
|
+
};
|
|
54
|
+
readonly quality: null;
|
|
55
|
+
readonly base: null;
|
|
56
|
+
}
|
|
57
|
+
export interface StructuralDims {
|
|
58
|
+
readonly entity_count: number;
|
|
59
|
+
readonly relation_count: number;
|
|
60
|
+
readonly attr_richness: number;
|
|
61
|
+
readonly lore_completeness: number;
|
|
62
|
+
readonly text_volume: number;
|
|
63
|
+
readonly asset_refs: number;
|
|
64
|
+
}
|
|
65
|
+
export declare function sat(x: number, k: number): number;
|
|
66
|
+
export declare function computeStructuralDims(input: StructuralInput): StructuralDims;
|
|
67
|
+
/** 与 Rust `compute_structural_dims_with_archetype` 保持同口径。 */
|
|
68
|
+
export declare function computeStructuralDimsWithArchetype(input: StructuralInput, features: ScenarioScoringFeatures, archetypeId: ArchetypeId): StructuralDims;
|
|
69
|
+
export declare function computeStructuralScoreWithArchetype(input: StructuralInput, features: ScenarioScoringFeatures, archetypeId: ArchetypeId): number;
|
|
70
|
+
export declare function sumStructuralDims(dims: StructuralDims): number;
|
|
71
|
+
export declare function computeStructuralScore(input: StructuralInput): number;
|
|
72
|
+
/** Host-facing descriptive alias;与历史 computeStructuralScore 使用同一公式实现。 */
|
|
73
|
+
export declare const computeScenarioStructuralScore: typeof computeStructuralScore;
|
|
74
|
+
export declare function buildStructuralInput(params: StructuralSourceInput): StructuralInput;
|
|
75
|
+
/** 本地顶部预估只使用结构事实;不接受 media 自报值,也不伪造 quality/base 或 dual-radar 轴。 */
|
|
76
|
+
export declare function computeLocalStructuralProjection(input: LocalStructuralProjectionInput): LocalStructuralProjection;
|