Merge branch 'develop' into feature/增加结尾保存节点

This commit is contained in:
2026-07-27 00:57:05 +08:00
38 changed files with 1797 additions and 336 deletions
+72
View File
@@ -0,0 +1,72 @@
---
name: yarn-text-audit
description: 全量审校 Yarn 文本,生成可追溯问题台账并可导出飞书多维表格
---
对用户指定的 Yarn 目录执行全量文本审校。确定性覆盖与安全门禁由 `Tools/yarn_text_audit.py` 提供;语言和叙事判断必须保持可复核。
## 执行流程
1. 明确目标目录、参考目录、是否包含注释、是否只出报告。
2. 对所有目标目录运行:
```powershell
python Tools/yarn_text_audit.py scan `
--root <Yarn目录可重复> `
--output Temp/YarnTextAudit `
--review-memory <可选上次飞书审阅导出的 reviewed-base.json>
```
3. 检查 `Temp/YarnTextAudit/summary.json`,必须满足:
- `unknown_active_lines == 0`
- `unreviewed_records == 0`
- `active_records == ledger.jsonl` 实际行数
4. 分文件、分节点复核 `ledger.jsonl`。活动文本包括命名对白、无角色旁白、系统文本、选项和 option prompt;排除首个非空白字符为 `//` 的注释、节点元数据和 Yarn 命令。
5. 将确定性错误标为“建议修改”,语义、节奏、专名和重要信息等判断标为“需确认”;不以自动规则冒充绝对正确。
6. 交付 `issues.csv``coverage.csv`。用户要求飞书时,先运行 `python Tools/yarn_text_audit.py lark-payloads --output Temp/YarnTextAudit`,再创建 `问题清单``覆盖报告``术语与样式` 三张表;单批不超过 200 条并核对返回数量。
7. 用户完成多维表格审阅后,导出完整记录并执行:
```powershell
python Tools/yarn_text_audit.py apply-review `
--reviews Temp/YarnTextAudit/reviewed-base.json `
--manifest Temp/YarnTextAudit/manifest.json `
--report Temp/YarnTextAudit/apply-report.json
# 预览无误后才实际写入
python Tools/yarn_text_audit.py apply-review `
--reviews Temp/YarnTextAudit/reviewed-base.json `
--manifest Temp/YarnTextAudit/manifest.json `
--report Temp/YarnTextAudit/apply-report.json `
--apply
```
8. 修改后重新扫描并运行 `verify`。不得新增、重生成或修改 `#line:` 标签;不得增加文本显示行数。
## 人工反馈优先级
- 后续扫描使用 `--review-memory`;脚本会按原文或已应用后的预期文本重建稳定 ID,不重复报告已经人工决策的记录。
- “按照备注修改”高于分类和脚本建议;只执行备注明确要求的断行、颜色、文字或效果。
- 不因 `Task` 说话者、`role="Aside"` 或专有名词出现本身自动判定颜色。必须结合是否为玩家行动目标、真实内心表达及当前叙事重点逐处判断。
- 专有名词不要求每次出现都标蓝;检查/拆除/短接/装回等玩家下一步操作可以按上下文标黄。
- 笑话效果优先只包裹双关或谐音落点,不默认包裹整句。
- `#autonext` 不应一律修正为 `#auto_next`;若审阅备注要求移除,则直接移除。
- 长行断点以约 18 个全角字符的实际 UI 宽度估算自动折行数,优先在逗号处显式控制;已有断行未经批准不重排。
## AIBIS 审校规则
- 检查错别字、错误重复、缺字候选、的地得、全半角和标点、标签配对及换行舒适度。
- 换行优先考虑逗号附近;只有在不增加显示行数时才建议调整。逗号停顿只用于调整后的断点或明确节奏位置。
- `[[aside]]...[[/aside]]`:实际自言自语、念文本或小声说话;斜体本身不是充分条件。
- `[[term]]...[[/term]]`:游戏专有名词,默认蓝色 `#52B6FF`
- `[[key]]...[[/key]]`:玩家必须关注的信息,优先级高于专名。
- `[[comma-pause]]`:可配置的 0.2 秒逗号停顿。
- `[[ulm]]`FP_Peipei2 中每条活动 `ULM自训练进程:` 文本。
- `[[joke]]...[[/joke]]`:火山实际讲出的笑话,不包括讨论笑话的说明对白。
- 本地输出放入已忽略的 `Temp/YarnTextAudit`;修改前保存文件 SHA-256,应用时必须精确匹配原文与哈希。
## 自检
```powershell
python Tools/yarn_text_audit.py self-test
python Tools/yarn_text_audit.py verify --output Temp/YarnTextAudit
```
Binary file not shown.
@@ -0,0 +1,21 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0df9c4430661480e9c647ea7e22a92b6, type: 3}
m_Name: NarrativeTextStyleConfig
m_EditorClassIdentifier:
_asideColor: {r: 0.37254903, g: 0.37254903, b: 0.37254903, a: 1}
_termColor: {r: 0.32156864, g: 0.7137255, b: 1, a: 1}
_keyColor: {r: 1, g: 0.69803923, b: 0, a: 1}
_commaPauseSeconds: 0.2
_ulmEffectPrefix: '[[vertexp d=0.05 bot]]'
_jokeOpenTag: <wiggle a=0.3 s=0.1>
_jokeCloseTag: </wiggle>
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b14d9162528749f68d1229a5de940dab
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ namespace AibisDream
public const string Root = "AIBIS";
public const string NarrativeTalkScene = Root + "/叙事/对话场景";
public const string NarrativeTextStyle = Root + "/叙事/文本样式";
public const string FrameAnimationGraph = Root + "/角色动画/帧动画 Graph";
public const string FrameClip = Root + "/角色动画/帧动画 Clip";
public const string BubbleStyle = Root + "/对话 UI/气泡样式";
@@ -29,6 +29,9 @@ namespace AibisDream
[SerializeField, Language]
private string _fallbackLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
[SerializeField]
private NarrativeTextStyleConfig _narrativeTextStyle;
public string AssetLocaleCode
{
get => _assetLocaleCode;
@@ -82,7 +85,9 @@ namespace AibisDream
line.Substitutions[i] = LocalizationKit.LocalizeParam(line.Substitutions[i]);
}
// 保留旧 LocalisedLineProvider 的文本转义处理
// 先展开项目级语义标记,再保留旧 LocalisedLineProvider 的文本转义处理
// ULM 的 [[vertexp ...]] 会由 Escape 转成 Text Animator 使用的花括号标签。
text = NarrativeTextStyleExpander.Expand(text, _narrativeTextStyle);
text = CommonUtil.Escape(text);
var parseResult = lineParser.ParseString(Yarn.Markup.LineParser.ExpandSubstitutions(text, line.Substitutions), LocaleCode);
@@ -0,0 +1,78 @@
using System.Globalization;
using UnityEngine;
namespace AibisDream
{
[CreateAssetMenu(
fileName = "NarrativeTextStyleConfig",
menuName = AibisAssetMenus.NarrativeTextStyle)]
public sealed class NarrativeTextStyleConfig : ScriptableObject
{
[Header("语义颜色")]
[SerializeField] private Color _asideColor = new Color32(0x5F, 0x5F, 0x5F, 0xFF);
[SerializeField] private Color _termColor = new Color32(0x52, 0xB6, 0xFF, 0xFF);
[SerializeField] private Color _keyColor = new Color32(0xFF, 0xB2, 0x00, 0xFF);
[Header("节奏与效果")]
[SerializeField, Min(0f)] private float _commaPauseSeconds = 0.2f;
[SerializeField] private string _ulmEffectPrefix = "[[vertexp d=0.05 bot]]";
[SerializeField] private string _jokeOpenTag = "<wiggle a=0.3 s=0.1>";
[SerializeField] private string _jokeCloseTag = "</wiggle>";
public Color AsideColor => _asideColor;
public Color TermColor => _termColor;
public Color KeyColor => _keyColor;
public float CommaPauseSeconds => _commaPauseSeconds;
public string UlmEffectPrefix => _ulmEffectPrefix;
public string JokeOpenTag => _jokeOpenTag;
public string JokeCloseTag => _jokeCloseTag;
}
public static class NarrativeTextStyleExpander
{
private static readonly Color DefaultAsideColor = new Color32(0x5F, 0x5F, 0x5F, 0xFF);
private static readonly Color DefaultTermColor = new Color32(0x52, 0xB6, 0xFF, 0xFF);
private static readonly Color DefaultKeyColor = new Color32(0xFF, 0xB2, 0x00, 0xFF);
public static string Expand(string text, NarrativeTextStyleConfig config)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
Color asideColor = config != null ? config.AsideColor : DefaultAsideColor;
Color termColor = config != null ? config.TermColor : DefaultTermColor;
Color keyColor = config != null ? config.KeyColor : DefaultKeyColor;
float commaPause = config != null ? config.CommaPauseSeconds : 0.2f;
string ulmPrefix = config != null
? config.UlmEffectPrefix
: "[[vertexp d=0.05 bot]]";
string jokeOpen = config != null
? config.JokeOpenTag
: "<wiggle a=0.3 s=0.1>";
string jokeClose = config != null ? config.JokeCloseTag : "</wiggle>";
return text
.Replace("[[aside]]", ColorOpen(asideColor))
.Replace("[[/aside]]", "</color>")
.Replace("[[term]]", ColorOpen(termColor))
.Replace("[[/term]]", "</color>")
.Replace("[[key]]", ColorOpen(keyColor))
.Replace("[[/key]]", "</color>")
.Replace(
"[[comma-pause]]",
$"<waitfor={commaPause.ToString("0.###", CultureInfo.InvariantCulture)}>")
.Replace("[[ulm]]", ulmPrefix)
.Replace("[[joke]]", jokeOpen)
.Replace("[[/joke]]", jokeClose);
}
private static string ColorOpen(Color color)
{
// This runs after Yarn has parsed the source, so the hash must not
// carry Yarn's source-level escape into TextMesh Pro.
return $"<color=#{ColorUtility.ToHtmlStringRGB(color)}>";
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0df9c4430661480e9c647ea7e22a92b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+31 -5
View File
@@ -8,6 +8,8 @@ public class DraggableUI : MonoBehaviour, IDragHandler, IPointerDownHandler
public RectTransform target;
public Button button;
private bool isLocked = false;
private Vector3 _dragOffset;
private float _dragScreenZ;
private void Awake()
{
@@ -16,22 +18,34 @@ public class DraggableUI : MonoBehaviour, IDragHandler, IPointerDownHandler
public void OnPointerDown(PointerEventData eventData)
{
// 这里可以添加按下时的逻辑,比如记录初始位置
if (isLocked) return;
Camera cam = ResolveCamera(eventData);
if (cam == null) return;
// 记录指针与物体的世界空间偏移,避免拖动开始时物体跳到指针中心。
_dragScreenZ = cam.WorldToScreenPoint(transform.position).z;
Vector3 pointerWorld = ScreenToWorld(cam, eventData.position, _dragScreenZ);
_dragOffset = transform.position - pointerWorld;
}
public void OnDrag(PointerEventData eventData)
{
if (isLocked) return;
Vector3 screenPosition = new Vector3(eventData.position.x, eventData.position.y,
Camera.main.WorldToScreenPoint(transform.position).z);
Vector3 move = Camera.main.ScreenToWorldPoint(screenPosition);
Camera cam = ResolveCamera(eventData);
if (cam == null) return;
Vector3 pointerWorld = ScreenToWorld(cam, eventData.position, _dragScreenZ);
Vector3 move = pointerWorld + _dragOffset;
move.z = transform.position.z;
transform.position = move;
}
void Update()
{
if (target == null || button == null) return;
float distance = Vector3.Distance(transform.position, target.position);
if (distance <= 1 && !isLocked)
@@ -53,4 +67,16 @@ public class DraggableUI : MonoBehaviour, IDragHandler, IPointerDownHandler
{
isLocked = false;
}
}
private static Camera ResolveCamera(PointerEventData eventData)
{
if (eventData != null && eventData.pressEventCamera != null)
return eventData.pressEventCamera;
return Camera.main;
}
private static Vector3 ScreenToWorld(Camera cam, Vector2 screenPosition, float screenZ)
{
return cam.ScreenToWorldPoint(new Vector3(screenPosition.x, screenPosition.y, screenZ));
}
}
@@ -388,7 +388,7 @@ namespace AibisDream.Kit
}
}
public void PlaySfx(string eventName, string audioKey = null)
public void PlaySfx(string eventName, string audioKey = null, float pitch = 1f)
{
var eventPath = FormatEventPath(eventName);
var key = string.IsNullOrEmpty(audioKey) ? eventName : audioKey;
@@ -405,6 +405,11 @@ namespace AibisDream.Kit
AddSfx(key, newSfx);
}
if (!Mathf.Approximately(pitch, 1f))
{
newSfx.setPitch(Mathf.Max(0.01f, pitch));
}
newSfx.start();
}
+42 -17
View File
@@ -50,7 +50,17 @@ namespace AibisDream
public EyeColorState GetCurrentColorState() => _currentState;
public float GetEffectTransitionDuration() => visualSettings.GetDurationForState(_currentState);
/// <summary>Eye/UF 演出倍速(默认见 Visual Tuning → Presentation Speed)。</summary>
public float PresentationSpeed => Mathf.Max(0.01f, visualSettings != null ? visualSettings.presentationSpeed : 1f);
public float ScaleDuration(float duration) => duration / PresentationSpeed;
public float GetEffectTransitionDuration() => ScaleDuration(visualSettings.GetDurationForState(_currentState));
private void PlayPresentationSfx(string eventName)
{
AudioManager.Instance.PlaySfx(eventName, pitch: PresentationSpeed);
}
/// <summary>仅「可想象色彩」上色时眨眼;无 autoBlink / 失焦眨眼。</summary>
private bool ShouldBlinkForImagine()
@@ -85,7 +95,8 @@ namespace AibisDream
visualSettings,
() => eyeStateEffect?.ApplyFocusedEffect(target, this),
visualSettings.imagineEffectAtBlinkOpen,
visualSettings.imagineBlinkGap);
visualSettings.imagineBlinkGap,
PresentationSpeed);
}
else
{
@@ -97,6 +108,7 @@ namespace AibisDream
private IEnumerator RunBlurOut(EyeTarget target, float duration)
{
duration = ScaleDuration(duration);
if (target != null)
{
target.BlurOut(duration);
@@ -279,12 +291,12 @@ namespace AibisDream
if (duration > 0.01f)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/visualturn");
PlayPresentationSfx("event:/ActionFB/visualturn");
}
mouseFollowAndZoom.MoveFocusTo(
focusTransform,
duration,
ScaleDuration(duration),
focusMinBounds,
focusMaxBounds,
GetFocusViewportSize());
@@ -404,7 +416,10 @@ namespace AibisDream
[YarnCommand("EyeBlink")]
public void EyeBlink(float duration = 0f)
{
EnsureViewportOverlay()?.PlayBlink(duration > 0f ? duration : null, restartAutoBlinkAfter: true);
EnsureViewportOverlay()?.PlayBlink(
duration > 0f ? duration : null,
restartAutoBlinkAfter: true,
presentationSpeed: PresentationSpeed);
}
[YarnCommand("set_Eyetarget")]
@@ -444,9 +459,10 @@ namespace AibisDream
}
const float focusMoveDuration = 1f;
float scaledFocusMove = ScaleDuration(focusMoveDuration);
FocusOnTarget(target, focusMoveDuration);
yield return WaitForFocusMovement(focusMoveDuration);
yield return WaitForFocusMovement(scaledFocusMove);
currentTarget = target;
mouseFollowAndZoom.SetIndicatorTarget(currentTarget);
@@ -483,7 +499,7 @@ namespace AibisDream
yield break;
}
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
yield return RunFocusedEffectWithPreBlink(currentTarget);
}
@@ -501,7 +517,7 @@ namespace AibisDream
yield break;
}
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
yield return RunUnfocusedEffect(currentTarget);
}
@@ -550,6 +566,7 @@ namespace AibisDream
public IEnumerator Set_Saturation(float targetSaturation, float duration)
{
targetSaturation = Mathf.Clamp01(targetSaturation);
duration = ScaleDuration(duration);
Sequence saturationSequence = DOTween.Sequence();
if (!TryJoinTargetSaturation(saturationSequence, targetSaturation, duration))
@@ -577,13 +594,14 @@ namespace AibisDream
Debug.LogWarning("[EyeSystem] 无可用 EyeTarget Glitch tween。", this);
}
yield return new WaitForSeconds(visualSettings.glitchInDuration);
yield return new WaitForSeconds(ScaleDuration(visualSettings.glitchInDuration));
}
[YarnCommand("EyeGlitch_out")]
public void EyeGlitch_out(float duration)
{
TryStopUnifiedGlitch(duration > 0f ? duration : visualSettings.glitchOutDuration);
float resolved = duration > 0f ? duration : visualSettings.glitchOutDuration;
TryStopUnifiedGlitch(ScaleDuration(resolved));
}
[YarnCommand("set_EyeColor")]
@@ -614,11 +632,17 @@ namespace AibisDream
memoryRender.sprite = memoryHandle.Result;
memoryRender.gameObject.SetActive(true);
float fadeIn = ScaleDuration(1.5f);
float hold = ScaleDuration(1.5f);
float squeeze = ScaleDuration(2f);
float fadeOut = ScaleDuration(0.5f);
float fadeOutAt = fadeIn + hold + squeeze;
Sequence memorySequence = DOTween.Sequence();
memorySequence.Append(memoryMaterial.DOFloat(0.85f, "_FullDistortionFade", 1.5f));
memorySequence.AppendInterval(1.5f);
memorySequence.Append(memoryMaterial.DOFloat(0f, "_SqueezePower", 2f));
memorySequence.Insert(4.5f, memoryMaterial.DOFloat(0f, "_FullDistortionFade", 0.5f).OnComplete(() =>
memorySequence.Append(memoryMaterial.DOFloat(0.85f, "_FullDistortionFade", fadeIn));
memorySequence.AppendInterval(hold);
memorySequence.Append(memoryMaterial.DOFloat(0f, "_SqueezePower", squeeze));
memorySequence.Insert(fadeOutAt, memoryMaterial.DOFloat(0f, "_FullDistortionFade", fadeOut).OnComplete(() =>
{
memoryRender.gameObject.SetActive(false);
}));
@@ -628,7 +652,7 @@ namespace AibisDream
Sequence sequence = DOTween.Sequence();
Sequence waveIn = CreateTargetRoundWaveSequence(1f, visualSettings.roundWaveInDuration);
Sequence waveIn = CreateTargetRoundWaveSequence(1f, ScaleDuration(visualSettings.roundWaveInDuration));
if (waveIn != null)
{
sequence.Append(waveIn);
@@ -636,7 +660,7 @@ namespace AibisDream
sequence.AppendCallback(() => { EyeGlitch_out(visualSettings.glitchOutDuration); });
Sequence waveOut = CreateTargetRoundWaveSequence(0f, visualSettings.roundWaveOutDuration);
Sequence waveOut = CreateTargetRoundWaveSequence(0f, ScaleDuration(visualSettings.roundWaveOutDuration));
if (waveOut != null)
{
sequence.Append(waveOut);
@@ -666,6 +690,7 @@ namespace AibisDream
public Tween TweenUnifiedUfWeights(float ascii, float dream, float memory, float duration)
{
duration = ScaleDuration(duration);
Sequence sequence = DOTween.Sequence();
bool hasTween = false;
@@ -710,7 +735,7 @@ namespace AibisDream
foreach (EyeTarget target in targets)
{
Sequence targetGlitch = target.StartUnifiedGlitch(visualSettings.glitchInDuration);
Sequence targetGlitch = target.StartUnifiedGlitch(ScaleDuration(visualSettings.glitchInDuration));
if (targetGlitch == null)
{
continue;
@@ -180,7 +180,10 @@ namespace AibisDream
}
}
public Tween PlayBlink(float? totalDuration = null, bool restartAutoBlinkAfter = false)
public Tween PlayBlink(
float? totalDuration = null,
bool restartAutoBlinkAfter = false,
float presentationSpeed = 1f)
{
if (instanceMaterial == null || activeSettings == null)
{
@@ -189,11 +192,12 @@ namespace AibisDream
blinkTween?.Kill();
float closeDuration = activeSettings.blinkCloseDuration;
float openDuration = activeSettings.blinkOpenDuration;
float durationScale = 1f / Mathf.Max(0.01f, presentationSpeed);
float closeDuration = activeSettings.blinkCloseDuration * durationScale;
float openDuration = activeSettings.blinkOpenDuration * durationScale;
if (totalDuration.HasValue)
{
float total = Mathf.Max(0.05f, totalDuration.Value);
float total = Mathf.Max(0.05f, totalDuration.Value) * durationScale;
closeDuration = total * 0.42f;
openDuration = total * 0.58f;
}
@@ -221,7 +225,8 @@ namespace AibisDream
EyeVisualSettings settings,
System.Action onEffectStart,
float effectAtOpenRatio,
float betweenBlinkGap = 0f)
float betweenBlinkGap = 0f,
float presentationSpeed = 1f)
{
if (!ShouldPlayTransitionBlink(settings))
{
@@ -234,11 +239,12 @@ namespace AibisDream
bool effectStarted = false;
blinkCount = Mathf.Max(1, blinkCount);
effectAtOpenRatio = Mathf.Clamp01(effectAtOpenRatio);
float durationScale = 1f / Mathf.Max(0.01f, presentationSpeed);
for (int i = 0; i < blinkCount; i++)
{
bool isLastBlink = i == blinkCount - 1;
Tween blink = PlayBlink();
Tween blink = PlayBlink(presentationSpeed: presentationSpeed);
if (blink == null)
{
if (isLastBlink && !effectStarted)
@@ -252,7 +258,8 @@ namespace AibisDream
if (isLastBlink)
{
float effectDelay = settings.blinkCloseDuration + settings.blinkOpenDuration * effectAtOpenRatio;
float effectDelay = (settings.blinkCloseDuration + settings.blinkOpenDuration * effectAtOpenRatio)
* durationScale;
yield return new WaitForSeconds(effectDelay);
if (!effectStarted)
{
@@ -267,7 +274,7 @@ namespace AibisDream
yield return blink.WaitForCompletion();
if (betweenBlinkGap > 0f)
{
yield return new WaitForSeconds(betweenBlinkGap);
yield return new WaitForSeconds(betweenBlinkGap * durationScale);
}
}
}
@@ -16,6 +16,10 @@ namespace AibisDream
[Tooltip("Focused / imagined color state.")]
[Range(0f, 1f)] public float colorRevealMax = 1f;
[Header("Presentation Speed")]
[Tooltip("Eye/UF 演出整体倍速(等待、Tween、眨眼、关联 SFX pitch)。2.5 = 快 2.5 倍。")]
[Min(0.01f)] public float presentationSpeed = 2.5f;
[Header("Transition Duration (seconds)")]
public float imagineColorDuration = 1f;
public float cannotImagineDuration = 1f;
+39 -15
View File
@@ -40,6 +40,16 @@ public class UFSystem : MonoBehaviour
memory
}
private float PresentationSpeed => eyeSystem != null ? eyeSystem.PresentationSpeed : 1f;
private float ScaleDuration(float duration) =>
eyeSystem != null ? eyeSystem.ScaleDuration(duration) : duration;
private void PlayPresentationSfx(string eventName)
{
AudioManager.Instance.PlaySfx(eventName, pitch: PresentationSpeed);
}
private void Awake()
{
FixSystemCenter.SystemDic.Register(this);
@@ -87,10 +97,11 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_spriteIn")]
public IEnumerator UF_spriteIn(float duration = 1.5f)
{
duration = ScaleDuration(duration);
var target = ResolveSpriteTarget();
if (target != null)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
target.ImageIn(duration);
}
@@ -100,10 +111,11 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_spriteOut")]
public IEnumerator UF_spriteOut(float duration = 1.5f)
{
duration = ScaleDuration(duration);
var target = ResolveSpriteTarget();
if (target != null)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
target.ImageOut(duration);
}
@@ -113,6 +125,7 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_AllspriteIn")]
public IEnumerator UF_AllspriteIn(float duration = 1.5f)
{
duration = ScaleDuration(duration);
foreach (EyeTarget target in eyeSystem.Targets)
{
target.ImageIn(duration);
@@ -124,6 +137,7 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_AllspriteOut")]
public IEnumerator UF_AllspriteOut(float duration = 1.5f)
{
duration = ScaleDuration(duration);
foreach (EyeTarget target in eyeSystem.Targets)
{
target.ImageOut(duration);
@@ -146,6 +160,7 @@ public class UFSystem : MonoBehaviour
Debug.LogWarning("[UFSystem] UF_faceIn: face.sprite 为空(可能是 aseprite 重导入后子资源 ID 失效)。", this);
}
duration = ScaleDuration(duration);
face.gameObject.SetActive(true);
face.DOFade(1f, duration);
yield return new WaitForSeconds(duration);
@@ -159,6 +174,7 @@ public class UFSystem : MonoBehaviour
yield break;
}
duration = ScaleDuration(duration);
face.DOFade(0f, duration);
yield return new WaitForSeconds(duration);
face.gameObject.SetActive(false);
@@ -167,7 +183,8 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_ImaginationEffectIn")]
public IEnumerator UF_ImaginationEffectIn(float duration = 1.5f)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
duration = ScaleDuration(duration);
PlayPresentationSfx("event:/ActionFB/color_imagine");
ClearUnifiedUfSpriteWeights();
TweenWeight(imageVolume, 1f, duration);
yield return new WaitForSeconds(0f);
@@ -176,15 +193,17 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_ImaginationEffectOut")]
public IEnumerator UF_ImaginationEffectOut(float duration = 1.5f)
{
duration = ScaleDuration(duration);
TweenWeight(imageVolume, 0f, duration);
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
yield return new WaitForSeconds(0f);
}
[YarnCommand("UF_MemoryEffectIn")]
public IEnumerator UF_MemoryEffectIn(float duration = 1.5f)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
duration = ScaleDuration(duration);
PlayPresentationSfx("event:/ActionFB/color_imagine");
ClearUnifiedUfSpriteWeights();
TweenWeight(finalVolume, 1f, duration);
yield return new WaitForSeconds(0f);
@@ -193,15 +212,17 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_MemoryEffectOut")]
public IEnumerator UF_MemoryEffectOut(float duration = 1.5f)
{
duration = ScaleDuration(duration);
TweenWeight(finalVolume, 0f, duration);
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
yield return new WaitForSeconds(0f);
}
[YarnCommand("UF_EyeTargetAscillIn")]
public IEnumerator UF_EyeTargetAscillIn(float duration = 1.5f)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
duration = ScaleDuration(duration);
PlayPresentationSfx("event:/ActionFB/color_imagine");
ClearUnifiedUfSpriteWeights();
TweenWeight(imageAscillVolume, 1f, duration);
yield return new WaitForSeconds(0f);
@@ -210,8 +231,9 @@ public class UFSystem : MonoBehaviour
[YarnCommand("UF_EyeTargetAscillOut")]
public IEnumerator UF_EyeTargetAscillOut(float duration = 1.5f)
{
duration = ScaleDuration(duration);
TweenWeight(imageAscillVolume, 0f, duration);
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
PlayPresentationSfx("event:/ActionFB/color_imagine");
yield return new WaitForSeconds(0f);
}
@@ -251,6 +273,7 @@ public class UFSystem : MonoBehaviour
public void UF_fightEffect()
{
ClearUnifiedUfSpriteWeights();
float loopDuration = ScaleDuration(1f);
scaleTween = DOTween.To(() => 0f, t =>
{
if (imageVolume != null)
@@ -267,7 +290,7 @@ public class UFSystem : MonoBehaviour
{
finalVolume.weight = Mathf.Lerp(1f, 0f, t);
}
}, 1f, 1f)
}, 1f, loopDuration)
.SetEase(Ease.InOutSine)
.SetLoops(-1, LoopType.Yoyo);
}
@@ -285,15 +308,15 @@ public class UFSystem : MonoBehaviour
while (isLoopImage)
{
eyeSystem.StartCoroutine(eyeSystem.SetTargetCoroutine(eyeSystem.Targets[index].name, false));
yield return new WaitForSeconds(0.5f);
yield return new WaitForSeconds(ScaleDuration(0.5f));
StartCoroutine(UF_spriteIn(0.5f));
StartCoroutine(UF_ImaginationEffectIn(0.5f));
eyeSystem.Targets[index].ImageIn(0.5f);
yield return new WaitForSeconds(0.5f);
eyeSystem.Targets[index].ImageIn(ScaleDuration(0.5f));
yield return new WaitForSeconds(ScaleDuration(0.5f));
StartCoroutine(UF_ImaginationEffectOut(0.5f));
StartCoroutine(UF_MemoryEffectIn(0.5f));
yield return new WaitForSeconds(1f);
eyeSystem.Targets[index].ImageOut(0.5f);
yield return new WaitForSeconds(ScaleDuration(1f));
eyeSystem.Targets[index].ImageOut(ScaleDuration(0.5f));
StartCoroutine(UF_MemoryEffectOut(0.5f));
index++;
if (index >= eyeSystem.Targets.Count - 2)
@@ -385,7 +408,8 @@ public class UFSystem : MonoBehaviour
yield break;
}
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
duration = ScaleDuration(duration);
PlayPresentationSfx("event:/ActionFB/color_imagine");
ClearUnifiedUfSpriteWeights();
switch (state)
+6 -6
View File
@@ -1,4 +1,4 @@
title: Start
title: Start
tags: start
colorID: 8
position: 205,-53
@@ -82,8 +82,8 @@ hs: 你说…为什么它们要我去卖帽子?卖帽子的冠军有什么用
hs: 筛选? #line:0261cd8
me: 治病要考核,销售要考核,上面用考核来筛选机体… #line:0a150b4
hs: 哪怕,我们并不需要帽子? #line:01bf733
me: 也许成为了冠军,上面会安排你去卖点有用的,电池、继电器,什么都行。 #line:020f059
hs: 可是,那些没有成为冠军的机体,它们的机生就浪费了吗?
me: 也许成为了冠军,上面会安排你去卖点有用的,[[comma-pause]]<br>电池、继电器,什么都行。 #line:020f059
hs: 可是,那些没有成为冠军的机体,[[comma-pause]]<br>它们的机生就浪费了吗?
hs: 浪费在卖一点用都没有的帽子上? #line:0fba1ea
->Cosplay人类 #line:0e60f3d
me: 是为了Cosplay人类吧,一种扮演。 #line:08aba07
@@ -96,11 +96,11 @@ hs: 也就是说,我整个生命周期,一开始就是错误? #line:085c4f
me: 火山……
hs: 路斯,告诉我吧,赢是什么感觉?#line:001cdc1
路斯瞥了眼火山帽子下的脑袋,抬起头,视线看向远方。 #line:0486ddc
路斯瞥了眼火山帽子下的脑袋,[[comma-pause]]<br>抬起头,视线看向远方。 #line:0486ddc
me: 到底是什么让你觉得我曾经*赢*过?
hs: 你当时说过的,你要在首都找到*治愈*机体的办法。
me: 最接近*治愈*的那一次,就是害我变成*问题分子*的手术。
me: 最接近*治愈*的那一次,[[comma-pause]]<br>就是害我变成*问题分子*的手术。
hs: 好吧…算了,那以后你也没再赢过了。
hs: 如果连你都赢不了,何况我呢?
@@ -124,7 +124,7 @@ hs: 我明白了。 #line:07c51d5
hs: 放心好了,我会加油的! #line:0fb16ed
hs: 一切还没有结束。我的比赛…也还没有结束。 #line:02e7cfc
火山重新戴好那顶有点歪的帽子,像个真正的推销员那样挺直了背。 #line:05e5cd3
火山重新戴好那顶有点歪的帽子,[[comma-pause]]<br>像个真正的推销员那样挺直了背。 #line:05e5cd3
hs: 如果你改变主意了,记得联系我。 #line:08d15bc
hs: 看在朋友的份上,我会给你报销额度内的最高折扣~ #line:0a63ca7
@@ -1,4 +1,4 @@
title: Start
title: Start
tags: start
colorID: 8
position: 205,-53
@@ -185,7 +185,7 @@ peipei: 医生…这片树叶是什么颜色的? #line:06a2bfc
->CenterOption: 绿色的。 #line:0baa89a
<<hide_dialog>>
<<wait 1>>
它伸出另一只手,用双手将它揉碎。树叶的粉末飘落下来。 #line:0ea37c1
它伸出另一只手,[[comma-pause]]<br>用双手将它揉碎。树叶的粉末飘落下来。 #line:0ea37c1
<<wait 2>>
peipei: 再见了,树叶。 #line:0c8a7ff
<<hide_dialog>>
@@ -194,7 +194,7 @@ peipei: 医生…这片树叶是什么颜色的? #line:06a2bfc
->CenterOption: 黄色的。 #line:003ecbc
<<hide_dialog>>
<<wait 1>>
它伸出另一只手,用双手将它揉碎。树叶的粉末飘落下来。 #line:0d2ae91
它伸出另一只手,[[comma-pause]]<br>用双手将它揉碎。树叶的粉末飘落下来。 #line:0d2ae91
<<wait 2>>
peipei: 再见了,树叶。 #line:055a0c7
+27 -27
View File
@@ -1,4 +1,4 @@
title: Start
title: Start
color: blue
cluster: Day2_Entry
tags: demo start
@@ -121,7 +121,7 @@ mecd: 快看!飞起来啦! #line:030481b
// 枯树长满了树叶,上面挂着一只孤零零的风筝,跟随着树冠一下一下地轻轻摇晃着。 #line:0f0d44b
// 树下站着一个扎着马尾辫的小女孩,正一下一下地拽着一条风筝线。风筝线通向树冠。 #line:08968b1
// mecd: 快下来吧。 #line:0bff6d0
<i>天上什么东西正在发出猎猎的声音,像涨满的风帆的抖动声。</i> #line:0a832a0
<i>天上什么东西正在发出猎猎的声音,[[comma-pause]]<br>像涨满的风帆的抖动声。</i> #line:0a832a0
<i>你向上看去。</i> #line:0718f75
<<tween_volume_weight_async "BloomEffect" 0.2 0>>
<<tween_volume_weight_async "BloomEffect" 0.001 4>>
@@ -143,7 +143,7 @@ position: 3,59
<<set $wind=1>>
<<kite_show {$h} {$wind} 0.35>>
<<kite_wind {$wind} 0>>
<i>一个小小的黑影在天上摆动着,时而向左时而向右。像一只自由自在的鸟儿,向天边飞去。</i> #line:07c785c
<i>一个小小的黑影在天上摆动着,[[comma-pause]]<br>时而向左时而向右。像一只自由自在的鸟儿,[[comma-pause]]<br>向天边飞去。</i> #line:07c785c
mecd: 喂!你怎么在愣神呀! #line:093133b
mecd: 该放线啦! #line:0beb13f
<<elseif $t==1>>
@@ -151,7 +151,7 @@ position: 3,59
<<kite_show {$h} {$wind} 0.35>>
<<kite_wind {$wind} 0>>
<i>哗啦………</i> #line:029246c
<i>鸟儿抖动着,乘着风向上方爬去,越来越远,越来越远…</i> #line:05cfc6c
<i>鸟儿抖动着,乘着风向上方爬去,[[comma-pause]]<br>越来越远,越来越远…</i> #line:05cfc6c
<i>它要去哪?</i> #line:0e76ca4
<i>你的手不由自主地攥紧了。</i> #line:02bc000
mecd: 会断的,该放松些啦! #line:0c0d82f
@@ -208,7 +208,7 @@ position: 3,59
mecd: 把它带走! #line:07ff984
mecd: 喂——!你听见我了吗——! #line:0c291b8
<<kite_accent taut>>
<i>鸟儿在空中抖动了起来。风变化着,线猛地绷成了一条直线。</i> #line:0d92e82
<i>鸟儿在空中抖动了起来。风变化着,[[comma-pause]]<br>线猛地绷成了一条直线。</i> #line:0d92e82
// <i>风变化着,线猛地绷成了一条直线。</i> #option_prompt
//<<elseif $t==6>>
<<set $wind=1>>
@@ -337,12 +337,12 @@ position: -636,163
// <<endif>>
<<if $gameStage==2>>
<<show_sprite "全景树与小女孩不出现小女孩">>
<i>空荡荡的游乐广场,树梢无精打采地,搅拌着沉闷的空气。女孩正尝试踮脚够着挂在树上的风筝。</i> #line:007bce1
<i>空荡荡的游乐广场,树梢无精打采地,[[comma-pause]]<br>搅拌着沉闷的空气。女孩正尝试踮脚够着挂在树上的风筝。</i> #line:007bce1
//<i>空荡荡的游乐广场,树梢无精打采地摇摆,搅拌着沉闷的空气。</i> #line:007bce1
<<elseif $gameStage==3>>
// <i>空荡荡的游乐广场,小女孩正坐在地上解开风筝的线</i> #line:0e54b9c
<<show_sprite "D2S广场">>
<i>空荡荡的游乐广场,风轻拂着树梢。一个扎着马尾辫的小女孩坐在长椅上。</i> #line:0e54b9c
<i>空荡荡的游乐广场,[[comma-pause]]<br>风轻拂着树梢。一个扎着马尾辫的小女孩坐在长椅上。</i> #line:0e54b9c
<<elseif $gameStage==4>>
<<show_sprite "D2S广场">>
<i>空荡荡的游乐广场,风很大,卷得树沙沙作响。</i> #line:06583cd
@@ -360,7 +360,7 @@ position: -636,163
->秋千 #line:06c60e9
<<show_sprite "D2S秋千">>
->坐 #line:002a7a1
你坐在上面,抬起双腿,轻轻地摇摆。秋千发出吱呀呀的声音。 #line:02afab6
你坐在上面,抬起双腿,[[comma-pause]]<br>轻轻地摇摆。秋千发出吱呀呀的声音。 #line:02afab6
你的屁股被挤得疼。这不是为你这么大的体型设计的。 #line:049b550
// (stage3可以和女孩一块坐?) #line:096bf2f
//这里可以增加一个小女孩拉下的东西?显示小女孩之前在这里?
@@ -369,11 +369,11 @@ position: -636,163
<<set_volume_weight "BloomEffect" 0.1>>
<<tween_volume_weight_async "BloomEffect" 0 2>>
<<show_sprite "D2S蓝天">>
天蓝蓝的,像湖水一样,亮得让你睁不开眼。一阵闷热的风撞在你的脸上,带着令你窒息的咸腥味和海鸥的叫声。 #line:058ebb4
天蓝蓝的,像湖水一样,[[comma-pause]]<br>亮得让你睁不开眼。一阵闷热的风撞在你的脸上,[[comma-pause]]<br>带着令你窒息的咸腥味和海鸥的叫声。 #line:058ebb4
->树<<if $gameStage==2>> #line:02f1319
<<show_sprite "D2S女孩1">>
<<if $gameStage==2>>
树下站着一个扎着马尾辫的小女孩,她的手上拽着一条细线,细线的另一头通向树冠。 #line:0446e8b
树下站着一个扎着马尾辫的小女孩,[[comma-pause]]<br>她的手上拽着一条细线,[[comma-pause]]<br>细线的另一头通向树冠。 #line:0446e8b
->抬头 #line:000b785
<<jump 树>>
<<else>>
@@ -399,7 +399,7 @@ position: -636,163
mecd:风里的那个声音。你没听到过吗? #line:0b2e108
mecd:我希望它经常来找我玩,这样它就会开心一点! #line:0659abc
风…吗? #line:0199246
沉闷的空气轻轻翻腾起来,掠过手臂上的汗毛,痒痒的。 #line:0b817f3
沉闷的空气轻轻翻腾起来,[[comma-pause]]<br>掠过手臂上的汗毛,痒痒的。 #line:0b817f3
mecd: 你听到了么?它来了。 #line:0ef7aee
mecd: 你是对的!起风了,快来! #line:0b1784f
<<show_sprite "D2S女孩4">>
@@ -454,7 +454,7 @@ position: -1026,193
mecd: 总是这样…… #line:019fa29
你注意到一条缠绕在树枝上的线,伸手扯住了它。 #line:0593280
mecd: 不,那样会把它系死在上面的。 #line:044efec
女孩仰着脑袋,头也不回地说。她纵身一跃,够着了树梢的末端。 #line:09a8ac4
女孩仰着脑袋,头也不回地说。她纵身一跃,[[comma-pause]]<br>够着了树梢的末端。 #line:09a8ac4
//你提示提示女孩松开手,风筝自然就会下来。 #line:0ab907a
扑倏倏… #line:01e1891
<<show_sprite "D2S树特写2">>
@@ -488,7 +488,7 @@ position: -1026,193
你试图摇晃粗大的树干。 #line:0b4eb71
树不为所动。 #line:07fd379
mecd: 没用的。你摇不动它的。 #line:04e2315
女孩无奈地看了你刚才试图摇动的树干,又抬起头盯着高处的风筝。 #line:0620f1a
女孩无奈地看了你刚才试图摇动的树干,[[comma-pause]]<br>又抬起头盯着高处的风筝。 #line:0620f1a
<<jump 拿下风筝>>
->跳起来试探 #line:0672dda
你轻轻一跃。 #line:0156ab5
@@ -591,10 +591,10 @@ position: 3,280
<<elseif $t==2>>
mecd: 快听! #line:004b85e
你竖起耳朵。 #line:0c8d155
风忽紧忽慢,杂乱无章…像是有很多声音,又像都只是噪声…… #line:041393a
风忽紧忽慢,杂乱无章…像是有很多声音,[[comma-pause]]<br>又像都只是噪声…… #line:041393a
med: 我好像没…… #line:0b0933d
<<kite_accent flutter>>
手里的线猛地收紧了。然后又被猛地松开,像潮汐一样,像… #line:066c8e4
手里的线猛地收紧了。然后又被猛地松开,[[comma-pause]]<br>像潮汐一样,像… #line:066c8e4
像呼吸一样。 #line:0b560d2
mecd: 保持高度。 #line:099e3cb
风再次吹了起来,手里的线收紧了。 #line:042ae7a
@@ -630,7 +630,7 @@ position: 3,280
mecd: 还记得吗?“不要因为不舍得离开,而忘记了告别。” #line:0494ee2
风静了下来。 #line:09cc698
mecd: 你帮它们看到了很多东西,这就很好啦! #line:0a4bcf5
一阵暖风刮过,像太阳的温度。紧接着又是一阵咸腥的海风。 #line:0153926
一阵暖风刮过,[[comma-pause]]<br>像太阳的温度。紧接着又是一阵咸腥的海风。 #line:0153926
它在回忆啊。 #line:0ed2355
<<kite_accent sink>>
风懒洋洋地飘送着。风筝轻轻地往下落。 #line:00317ee
@@ -674,7 +674,7 @@ position: 3,280
风没有回应,呼啸、盘旋着,向上而去。#option_prompt
<<elseif $t==10>>
<<kite_accent taut>>
一条笔直的垂线指向天空,一头是已经几乎看不见的风筝,一头是你紧拽着的双手。 #line:051e887
一条笔直的垂线指向天空,[[comma-pause]]<br>一头是已经几乎看不见的风筝,[[comma-pause]]<br>一头是你紧拽着的双手。 #line:051e887
med: 慢一点…慢一点…… #line:02416d6
mecd: 喂!别忘了!你为什么要成为现在的你!别忘了啊! #line:0d643bf
mecd: 我会恨你的!恨你一辈子! #line:03f2d20
@@ -685,7 +685,7 @@ position: 3,280
<<elseif $t==11>>
<<kite_accent wrench>>
<<set_global_param d2sAccend 1>>
轱辘发出咚的一声,停止了转动。你被拽得向前走了两步,几乎要被带离地面。 #line:0629fc9
轱辘发出咚的一声,[[comma-pause]]<br>停止了转动。你被拽得向前走了两步,几乎要被带离地面。 #line:0629fc9
所有的线都被放出去了。<br>你知道只有一个办法让它飞得更高了。 #line:002fd88
<<show_sprite_motion "D2S轱辘没线" "reel_tension" 0.25>>
->放手 #line:0e492d7
@@ -842,13 +842,13 @@ position: 0,619
zls?: …所以…那个时候…
zls?: 得知她去世的时候。你是什么感觉?
mem: 我不记得了。
zls: 如果你真的想忘掉,现在就必须记起来。否则β-阻滞剂就无法生效。
zls: 如果你真的想忘掉,[[comma-pause]]<br>现在就必须记起来。否则β-阻滞剂就无法生效。
<<wait 1>>
mem: 害怕。
zls: 害怕?
mem: 我害怕未来,因为我意识到接下来的一辈子我都将在后悔中度过。
mem: 我害怕未来,[[comma-pause]]<br>因为我意识到接下来的一辈子我都将在后悔中度过。
zls: 那么你后来后悔吗?
<<wait 1>>
@@ -961,7 +961,7 @@ cluster: Day2_Door
position: -4,927
---
<<show_large_sprite "D2S枯树-Large" 0>>
洁白的空气延伸到视线的尽头,正中间是一棵早已枯萎的老树,一动不动。 #line:076531b
洁白的空气延伸到视线的尽头,[[comma-pause]]<br>正中间是一棵早已枯萎的老树,一动不动。 #line:076531b
<<if $fz==false>>
一张淡红色的什么东西在树下静静地躺着。 #line:0293d5e
<<else>>
@@ -1115,7 +1115,7 @@ position: 326,937
wyd?: 她要给出答案了吗? #line:0af3672
你吓了一跳。声音是从很近的地方传来的。你的正上方。 #line:05f60e0
<<show_large_sprite "门上乌鸦中景金色" 0>>
一只黑色的鸟站在门顶上,歪着脑袋,一动不动地看着远方。 #line:0a6b416
一只黑色的鸟站在门顶上,[[comma-pause]]<br>歪着脑袋,一动不动地看着远方。 #line:0a6b416
me: 你是谁? #line:07e8aa5
//<<show_large_sprite "D2S戈塔什乌鸦静态" 0>>
wyd?: 真的存在吗?好好想想。 #line:0d5db81
@@ -1137,19 +1137,19 @@ wyd?: 真的存在吗?好好想想。 #line:0d5db81
<<show_door_listen_view true 0>>
<i>你再次看向门,脑中充满了疑惑。</i>
me: 但这为什么感觉如此真实?这个梦到底又有什么意义……
me: 海对人类…到底有什么吸引力?那只是一滩水而已,不是么…
me: 海对人类…到底有什么吸引力?那只是一滩水而已,[[comma-pause]]<br>不是么…
->不知道 #line:0374e64
<<set $seaExist="我不知道…也许?">>
<<show_door_listen_view true 0>>
<<wait 1>>
me: 我…不知道。没人见过它。 #line:00b6413
me: 我知道这不符合规律,但是…为什么我会想要……相信……?
me: 我知道这不符合规律,[[comma-pause]]<br>但是…为什么我会想要……相信……?
<<tween_door_closeup_async 1.3 1 20>>
me: 我知道这很危险。可是…这真的不太正常。这已经不是第一次了……
me: 为什么它总是…在……在呼唤我?
<<wait 1>>
你不知道为什么要和一个乌鸦讨论这个问题。这很荒唐。 #line:0f43a8e
但你忽然意识到……这只黑色的生物,不是梦的一部分。
但你忽然意识到……这只黑色的生物,[[comma-pause]]<br>不是梦的一部分。
<<show_large_sprite "门上乌鸦中景金色" 0>>
<<set_door_closeup 1.3 1>>
<<wait 1>>
@@ -1259,9 +1259,9 @@ me: …为什么……不是…吗?
乌鸦: 原型。一种混沌的系统。这个世界上最不优雅的程序。
乌鸦: 它发散,随机,似乎有无限种可能……
<<tween_door_closeup_async 2.2 1 20>>
乌鸦: 但有一种可能,潜伏在它自己的阴影之下,那是它无论如何也无法给出的…
乌鸦: 但有一种可能,潜伏在它自己的阴影之下,[[comma-pause]]<br>那是它无论如何也无法给出的…
<<wait 1>>
乌鸦: 作为一个底层校验代码,这个问题…只能接受一种类型的参数。 #line:0cefd12
乌鸦: 作为一个底层校验代码,[[comma-pause]]<br>这个问题…只能接受一种类型的参数。 #line:0cefd12
乌鸦: 0 ,和 1 。 #line:0aa2f5d
//乌鸦: 作为一个底层系统,那是纯粹的脉冲。 #line:0cefd12
乌鸦: 当她开始像原型一样思考的时候…<br>…一个不可能被回答的问题就会发现她…… #line:07a0f97
+6 -6
View File
@@ -1,4 +1,4 @@
title: Stage1
title: Stage1
tags: start
group: 入口
colorID: 2
@@ -63,7 +63,7 @@ hs: 好——冷——啊——! #line:032e2c6
<<wait 1>>
hs: 感觉到头冷,头空,头寂寞,头焦虑型依恋障碍吗? #line:04684d1
hs: 戴上"实实"牌帽子,给你的 *头* 顶添件 *宝* #line:0c53475
hs: 戴上实实牌帽子,给你的 *头* 顶添件 *宝* #line:0c53475
me: ……什么…东西? #line:0924ddd
@@ -118,7 +118,7 @@ hs: 你那是末端传感器坏死导致的头憋大了。 #line:05929a0
<<wait 0.1>>
<<stop_actor_shake 火山>>
<<wait 0.5>>
hs: 但今天我要恭喜你。我为你带来了这款限定版多功能大头弱化器,有效在视觉上解决头太大的问题。 #line:05c5927
hs: 但今天我要恭喜你。我为你带来了这款限定版多功能大头弱化器,[[comma-pause]]<br>有效在视觉上解决头太大的问题。 #line:05c5927
me: 只能是视觉上么?…<br><waitfor=0.4>不对,<waitfor=0.2>我头也不大啊。 #line:01dbe17
@@ -126,7 +126,7 @@ hs: 那还是没买这款多功能大头弱化器帽子的问题。 #line:050506
<<change_actor_state "伸手石化表情" 火山>>
<<play_timeline "彩带">>
<<wait 0.3>>
hs: 只要你头顶戴帽儿了,烦恼就进套儿了,聪明的智商就又能上号儿了。 #line:04500c3
hs: 只要你头顶戴帽儿了,烦恼就进套儿了,[[comma-pause]]<br>聪明的智商就又能上号儿了。 #line:04500c3
me: 这又是从哪抄的词?? #line:0a13852
me: 好了好了…我还要工作呢,你就别折腾我了。 #line:0bb2b87
@@ -179,7 +179,7 @@ me: 啊不是等会什么… #auto_next #line:09fc837
<<change_actor_state "摘帽" 火山>>
hs: 你还在为帽子没用而烦恼吗?保暖?只有劣质机体才会需要那种功能! #line:04adf2a
me: 还来??#auto_next #line:06d2b78
hs: 帽子!已经摆脱了庸俗的实用价值,成为了尊贵高级型号的符号象征! #line:0f77e23
hs: 帽子!已经摆脱了庸俗的实用价值,[[comma-pause]]<br>成为了尊贵高级型号的符号象征! #line:0f77e23
me: 放过我吧……#auto_next #line:06d1bd0
hs: 头牌帽子,每名机体, *医生* 仅能定制一顶。 #line:0ccb0c9
@@ -197,7 +197,7 @@ hs: 不——搞——笑?? #line:0101ec0
hs: 试试这个! #line:02e72ae
<<change_actor_state "伸手表情idle" 火山>>
<<wait 1>>
hs: 请问,一个苹果得病了去医院,却被赶了出来,为什么? #line:0bb268a
hs: 请问,一个苹果得病了去医院,[[comma-pause]]<br>却被赶了出来,为什么? #line:0bb268a
me: 你能哪怕一秒钟不要再说这些笑话了么…你今天是怎么回事? #line:000a69c
<<change_actor_state "拿住帽子" 火山>>
<<wait 1>>
+21 -21
View File
@@ -1,4 +1,4 @@
title: Stage2
title: Stage2
tags: start
group: 入口
colorID: 2
@@ -26,7 +26,7 @@ dn: 系统已就绪。请使用插头进行检查。
<<DropCable>>
<<fade_out 1>>
//me: 患者的生命指标异常,异常表现为似乎无法停止说笑话。 #line:00d5e4f
me: 我需要检查一下它的表达模块。<br>…或许是幽默单元这块有点问题。 #line:0812f26
me: 我需要检查一下它的[[key]]表达模块[[/key]]。<br>…或许是幽默单元这块有点问题。 #line:0812f26
<<show_task_panel>>
<<clear_tasks>>
Task: 检查表达模块 #line:0f2a6c8
@@ -80,9 +80,9 @@ EmoModule: 已与情绪模块建立连接! #line:0b19318
hs: 医生,会不会搞错啦。 #line:0bc440f
me: 理论上…不会。这就是你在说刚才那句话的时候的情绪。 #line:0e89018
me: 具体来说,你是在…因为什么而紧张? #line:08e2d23
hs: 医生,我一点也不紧张。一想到我要接受检查,还不知道结果,我就感觉很放松。 #line:0fb90cc
hs: 医生,我一点也不紧张。一想到我要接受检查,[[comma-pause]]<br>还不知道结果,我就感觉很放松。 #line:0fb90cc
me: 真的? #line:0748e07
hs: ……医生,我的意思是,正常机体到诊所,都会感觉紧张的吧! #line:0e12d2c
hs: ……医生,我的意思是,[[comma-pause]]<br>正常机体到诊所,都会感觉紧张的吧! #line:0e12d2c
me: …这倒也是。一到诊所就紧张,确实是机之常情。 #line:05a3a96
me: 难道说问题不在于这个信号的源头…而在于它的处理过程? #line:08eb820
me: 可是均衡器刚才查下来也没有问题啊。一定是漏了什么…让我捋捋。 #line:074c7b2
@@ -118,7 +118,7 @@ MemModule: 已与记忆模块建立连接! #line:00bc46a
MemModule: 检查完成!未发现错误。 #line:0600a21
<<wait 0.1>>
me: 火山,为什么我会回幸福市?
hs: 为了测试我,甚至不惜戳自己十年前的痛处吗,*问题分子*?
hs: 为了测试我,[[comma-pause]]<br>甚至不惜戳自己十年前的痛处吗,*问题分子*?
me: ……记忆模块正常。 #line:0e89254
<<hide_dialog>>
<<set $memoryChecked = true>>
@@ -154,10 +154,10 @@ LogicModule: 已与逻辑模块建立连接! #line:0a019ad
<<set $logicChecked = true>>
<<endif>>
LogicModule: 最近的逻辑处理进程: #line:025e347
LogicModule: 赢下销售大赛,赢下销售大赛,赢下销售大赛,赢下销售大赛…… #line:0b4eed1
LogicModule: 赢下销售大赛,赢下销售大赛,[[comma-pause]]<br>赢下销售大赛,赢下销售大赛…… #line:0b4eed1
me: 火山,你领导和我同时掉进河里,你先救谁?
hs: 我的机型进水会出故障的,我帮你们联系专业的警员机体。
me: 看来逻辑模块正常。 #line:0e9b752
hs: 我的机型进水会出故障的,[[comma-pause]]<br>我帮你们联系专业的警员机体。
me: 看来[[term]]逻辑模块[[/term]]正常。 #line:0e9b752
<<hide_dialog>>
// <<if $speakerChecked==true && $colorChecked==true>>
// // Stage2: 检查完表达和滤波器后,在滤波器输出中扫描寻找逻辑成分
@@ -219,7 +219,7 @@ EyeModule: 已与视觉模块建立连接! #line:0ac783f
me:它好像真的有点失望。 #line:04413be
<<hide_dialog>>
<<else>>
me: 视觉模块正常。 #line:05e45e9
me: [[term]]视觉模块[[/term]]正常。 #line:05e45e9
<<endif>>
->结束 #line:0e66d9b
<<hide_dialog>>
@@ -248,11 +248,11 @@ SpeakerModule: 已与表达模块建立连接! #line:027b73c
<<if $speakerChecked == true>>
// Stage2 重复插线:简短总结 + 下一步提示
SpeakerModule: 表达模块状态正常。 #line:0e3a80c
me: 检查结果显示…笑话不是表达模块产生的,是从外部注入的。 #line:0a3b7e1
me: 检查结果显示…笑话不是表达模块产生的,[[comma-pause]]<br>是从外部注入的。 #line:0a3b7e1
<<if $colorChecked == false>>
me: 应该去查查*滤波器*,看看信号来源。 #line:0a3b7e2
<<else>>
me: 应该去检查*情绪模块*,确认信号源头。 #line:0a3b7e3
me: 应该去检查[[key]]情绪模块[[/key]],确认信号源头。 #line:0a3b7e3
<<endif>>
<<else>>
// Stage2: 检查表达模块 - 模块健康,但笑话生成总被触发
@@ -260,7 +260,7 @@ SpeakerModule: 已与表达模块建立连接! #line:027b73c
SpeakerModule: 幽默单元:正常 #line:0924817
SpeakerModule: 悲伤表达单元:正常 #line:0985d82
SpeakerModule: 焦虑表达单元:正常 #line:03b41be
me: 幽默单元整体健康。没有报错,也没有警告,就是占用率很高… #line:0df4a8a
me: 幽默单元整体健康。没有报错,[[comma-pause]]<br>也没有警告,就是占用率很高… #line:0df4a8a
me: 难道说患者是自愿说笑话的? #line:01b8ee2
hs: 医生…难道说……<br>你是自愿来看病的吗? #line:0992329
me: …… #line:04d09a9
@@ -268,16 +268,16 @@ SpeakerModule: 已与表达模块建立连接! #line:027b73c
SpeakerModule: 正在关闭幽默单元… #line:0ea7cf8
SpeakerModule: 幽默单元已关闭! #line:0a2d1e1
me: 再说句话试试。 #line:0114072
hs: 医生你知道吗,看医生其实是被医生看。 #line:0ac5a55
hs: 医生你知道吗,[[joke]]看医生[[/joke]]其实是[[joke]]被医生看[[/joke]]。 #line:0ac5a55
me: …… #line:0ae6738
me: 奇怪了…关闭了幽默单元,怎么还会说笑话呢…… #line:0a012a9
me: 难道说… #line:0a6b8a3
Analysing: 正在分析原始语义信号… #line:05595cc
Analysing: 200-500hz 占用率:9% #line:04a52a2
Analysing: 500-2000hz 占用率:12% #line:083c972
Analysing: 2000-1000hz 占用率:4% #line:00fba06
Analysing: 2000-10000hz 占用率:4% #line:00fba06
Analysing: 10000-20000hz 占用率:99% #line:02ada4c
me: 哈!果然。这个笑话不是患者"说"出来的… #line:093e81e
me: 哈!果然。这个笑话不是患者“说”出来的… #line:093e81e
me: …而是被注入的。 #line:0fd40de
me: 销售模块的语义信号……<br>应该都是从这里发出的。 #line:0428eda
me: 检查一下语义信号的来源吧。 #line:04c3d68
@@ -311,7 +311,7 @@ SpeakerModule: 已与表达模块建立连接! #line:027b73c
Error: 系统保护性中断。请释放内存。 #line:08b6066
<<clear_errors>>
<<wait 1>>
me:看到了么。这七年那些被均衡器压缩掉的情绪log,其实依然存在这里。 #line:049696d
me:看到了么。这七年那些被均衡器压缩掉的情绪log,[[comma-pause]]<br>其实依然存在这里。 #line:049696d
//me:只是表达被替换成了笑话,真实的情绪log却一直堆积在这里 #line:07c2eb4
me:这七年持续的累积,只是碰巧在今天才卡死。 #line:02c6f11
hs: 我还以为…是因为昨晚的事情。 #line:02d5a53
@@ -349,7 +349,7 @@ SaleModule: 已与销售语言拓展模块建立连接! #line:0448f12
<<endif>>
<<wait 0.1>>
<<hide_dialog>>
SaleModule: 信号接正常,信号来源: #line:0434e7c
SaleModule: 信号接正常,信号来源: #line:0434e7c
SaleModule: 情绪模块,逻辑模块 #line:0fc4164
Analysing:------ #line:0e0bb6d
SaleModule: 语义组织器运转正常 #line:041265f
@@ -359,8 +359,8 @@ SaleModule: 已与销售语言拓展模块建立连接! #line:0448f12
<<if $gameStage==2 && $speakerChecked==true>>
<<if $colorChecked == true>>
// Stage2 重复插线:简短总结 + 下一步提示
me: 之前查过了,滤波器输出端有大量笑话信号,反演算指向情绪模块。 #line:0c7d8a1
me: 应该去检查*情绪模块*,确认情绪信号的源头。 #line:0c7d8a2
me: 之前查过了,滤波器输出端有大量笑话信号,[[comma-pause]]<br>反演算指向[[term]]情绪模块[[/term]]。 #line:0c7d8a1
me: 应该去检查[[key]]情绪模块[[/key]],确认情绪信号的源头。 #line:0c7d8a2
<<else>>
// Stage2: 检查滤波器 - 发现锯齿波(笑话波形)
me: 作为一个调谐模块本身没什么问题。让我看看之前提取的频域数据…… #line:0df2738
@@ -386,10 +386,10 @@ SaleModule: 已与销售语言拓展模块建立连接! #line:0448f12
me: 难道说患者的情绪…有问题? #line:0160020
hs: 医生,我的头天天呆在衣帽间,情绪怎么可能不好啊。 #line:054a0b2
me: 啥意思? #line:0585602
hs: 你知道一般来说,当我戴上帽子之后,我的头就处于 *衣* *帽* 间了。 #line:02d7b5c
hs: 你知道一般来说,当我戴上帽子之后,我的头就处于 *[[joke]]衣[[/joke]]* *[[joke]]帽[[/joke]]* 间了。 #line:02d7b5c
me: 差不多得了。 #line:0b2a925
hs: 好的医生。 #line:0b2645b
me: 总之去情绪模块查一下这个信号来源吧。 #line:035c378
me: 总之去[[key]]情绪模块[[/key]]查一下这个信号来源吧。 #line:035c378
<<complete_task "line:03f59d6">>
<<clear_tasks>>
<<hide_emotion_wave 1>>
+10 -10
View File
@@ -1,4 +1,4 @@
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
// Stage3: 滤波器深入检查
// 目标:1.进入步进查看模式,显示情绪输入波形
// 2.语义合成/语言审查/语气调整 由【销售模块转动完成】监听驱动(转1/2/3)
@@ -79,7 +79,7 @@ hs: 压缩…意味着什么? #line:00a336f1
me: 意味着患者正在克制自己的情绪。 #line:00a336f
// 此处结束,等待 SalesManager 转动完成事件 → 销售模块转动完成(turn 1/2/3)
me: 接下来我会转动测试发条,这个过程你可能会看到一些东西。那是情绪复现,正常现象。 #line:040d560
me: 接下来我会转动测试发条,[[comma-pause]]<br>这个过程你可能会看到一些东西。那是情绪复现,[[comma-pause]]<br>正常现象。 #line:040d560
<<set_crank_highlight true>>
===
@@ -119,7 +119,7 @@ position: 350,1500
// 转1完成时由 EventNode 销售模块转动完成 桥接入
SaleModule: 语义合成完成 #line:0a9f0e6
me: 一切正常…你的表达整体链路是健康的。 #line:0e7dffe
me: 接下来只要继续转发条,直到测试器跑完整个环就好了。 #line:094de1f
me: 接下来只要继续转发条,[[comma-pause]]<br>直到测试器跑完整个环就好了。 #line:094de1f
<<clear_tasks>>
Task: 继续顺时针上紧发条,完成处理链路检查(2/3)
===
@@ -136,7 +136,7 @@ position: 350,1630
SaleModule:>幅度正常,检测通过 #line:007a677
me: 嗯… #line:087c52b
hs: 怎么了,医生? #line:087c52c1
me: 没什么,一切正常。就是感慨一下,你的违禁词库好大啊。 #line:087c52c2
me: 没什么,一切正常。就是感慨一下,[[comma-pause]]<br>你的违禁词库好大啊。 #line:087c52c2
<<clear_tasks>>
Task: 继续顺时针上紧发条,完成处理链路检查(3/3)
===
@@ -211,7 +211,7 @@ me: 原来如此…这里有一个加装的旋钮。<br>怪不得…怪不得。
me: 97%的转换率。也就是说现在你的任何情绪信号,<br>都会被它强制压缩成一种失真的幽默信号…… #line:048af66
me: 这就是让火山无法停止说笑话的原因。 #line:0680a40
hs: 什么东西?医生,我听不懂。 #line:05aeda6
me: 简单说:这个旋钮,可以把任何情绪波都转变成笑话。而它现在处在拉满的位置。 #line:066ae42
me: 简单说:这个旋钮,[[comma-pause]]<br>可以把任何情绪波都转变成笑话。而它现在处在拉满的位置。 #line:066ae42
<<open_knob_lid 0.5>>
<<wait 0.3>>
me: 先试试关掉旋钮这个旋钮吧。
@@ -244,7 +244,7 @@ position: 350,2280
SaleModule: 【错误】已重新设置为97.3 #line:0a2d0b2
me: 好吧。果然关不掉。 #line:0c53bbc
me: 自动调节失效,手动也关不掉……这是个临时装置。 #line:09e3d75
me: 当时给你装这个东西的,不论是谁,肯定没打算让这个东西长期运行。 #line:00a63b1
me: 当时给你装这个东西的,不论是谁,[[comma-pause]]<br>肯定没打算让这个东西长期运行。 #line:00a63b1
me: 火山。当时是谁给你装的这个东西? #line:0ebd6c6
hs: 我有点…不太记得了。 #line:049b3ba
me: 没关系,我可以查。 #line:04d450d
@@ -278,7 +278,7 @@ position: 350,2280
me: 嗯。这下就全明白了… #line:0288a4a
<<show_full_screen "hs1_病历1-1">>
<<show_full_screen "hs1_病历1-2">>
hs: ……<waitfor=0.5>当时那个医生只说,给我装上这个,我就不怕结巴了… #line:087e780
hs: ……<waitfor=0.5>当时那个医生只说,[[comma-pause]]<br>给我装上这个,我就不怕结巴了… #line:087e780
me: 医生没有和你说要复诊吗? #line:051f73f
hs: ……<waitfor=0.5>有。 #line:0ee348f
me: 那你去复诊了吗? #line:0984c16
@@ -286,10 +286,10 @@ position: 350,2280
me: 七年前,你装上的时候,那个医生就和你说过吧。<br>一个月后复诊。 #line:0727e09
me: 你为什么不去复诊? #line:00778c4
hs: 因为我…一直以来……都感觉身体挺好的嘛。 #line:0211ff0
me: 你以为你感觉好,你的身体就真的没问题吗?为什么不听医生的话? #line:0e46b56
me: 你以为你感觉好,[[comma-pause]]<br>你的身体就真的没问题吗?为什么不听医生的话? #line:0e46b56
<<hide_full_screen>>
me: 情绪数据在发出之后,是需要得到反馈才会消除的。这七年来,你每一个被压缩的情绪… #line:03ac325
me: 都堆在表达模块的RAM里。 #line:0767e58
me: 情绪数据在发出之后,[[comma-pause]]<br>是需要得到反馈才会消除的。这七年来,[[comma-pause]]<br>你每一个被压缩的情绪… #line:03ac325
me: 都堆在[[key]]表达模块[[/key]]的RAM里。 #line:0767e58
me: 让我们去看看吧。 #line:01d4ab6
hs: 医生… #line:033d8cf
// 解锁旋钮,等待玩家尝试调节(KnobController 失败时跳 处理器单次调节失败 → 旋钮调节失败)
+5 -1
View File
@@ -11,6 +11,10 @@ position: 500,1200
<<show_task_panel>>
<<wait 0.3>>
<<clear_tasks>>
Task: 检查表达模块 #line:032ec2b
<<DropCable>>
<<wait 0.3>>
<<jump Stage4就绪>>
//me: 这像是被“塞住”了,我去检查一下【表达模块】 #line:0f210e7
===
@@ -22,7 +26,7 @@ position: 500,1350
Task: 检查表达模块 #line:032ec2b
<<DropCable>>
<<wait 0.3>>
me: 去*表达模块*深入检查,看看RAM里的淤积情况吧… #line:0f8357d
me: 去*[[key]]表达模块[[/key]]*深入检查,看看RAM里的淤积情况吧… #line:0f8357d
===
title: 结束Stage4
+15 -15
View File
@@ -1,4 +1,4 @@
title: Stage5
title: Stage5
tags: start
colorID: 2
position: 500,1500
@@ -25,22 +25,22 @@ me: … #line:0d92c92
me: 你不打算说也没关系。 #line:0860155
me: 不过…火山,那些情绪不会消失。 #line:06a5ca6
me: 它们只是被堆在某个地方。久而久之,已经堵塞无法再进行表达了。 #line:01bffd6
me: 七年的真实情绪堆积,已经把你的表达模块卡死了。 #line:0c63315
me: 它们只是被堆在某个地方。久而久之,[[comma-pause]]<br>已经堵塞无法再进行表达了。 #line:01bffd6
me: 七年的真实情绪堆积,已经把你的[[key]]表达模块[[/key]]卡死了。 #line:0c63315
<<change_actor_state "对手指切屏特效" 火山>>
<<wait 0.3>>
<<change_actor_state "对手指表情idle" 火山>>
<<wait 0.3>>
hs: 好吧~医生,看来我的内部消息还是得要告诉你啦。你不要往外说喔。 #line:0ce4ade
hs: 好吧~医生,[[comma-pause]]<br>看来我的内部消息还是得要告诉你啦。你不要往外说喔。 #line:0ce4ade
hs: 我一开始和你说的是真的。我没有工作了。帽子销售线停啦。 #line:0ce4adf
<<change_actor_state "伸手切屏特效" 火山>>
<<wait 0.3>>
<<change_actor_state "伸手表情idle" 火山>>
<<wait 0.3>>
hs: 当时英理正在给我进行年审考核,让我模拟推销。你知道,我是明星销售毕竟。值得英理亲自来审。 #line:0ce4ae0
hs: 可是没想到我这一桌菜,来了两桌客。就在英理蠢蠢欲动准备自掏腰包的时候,它们进来了。 #line:0ce4ae1
hs: 当时英理正在给我进行年审考核,[[comma-pause]]<br>让我模拟推销。你知道,[[comma-pause]]<br>我是明星销售毕竟。值得英理亲自来审。 #line:0ce4ae0
hs: 可是没想到我这一桌菜,[[comma-pause]]<br>来了两桌客。就在英理蠢蠢欲动准备自掏腰包的时候,[[comma-pause]]<br>它们进来了。 #line:0ce4ae1
hs: "你可以停下了,"它们看到我就说,"从现在起你和你的小组等待指示"。 #line:0ce4ae2
me: 它们? #line:06a5ca7
@@ -55,7 +55,7 @@ me: 效率审查司? #line:06a5ca9
<<change_actor_state "扣头灯泡" 火山>>
<<wait 0.3>>
hs: 对。就是上次把118辖区撸掉一半机体的那群*撸sir*。 #line:06a5caa
hs: 总之当时一下子就挤进来7个效率审查司的,满满当当,可以说是哄堂大*效*了。 #line:06a5cab
hs: 总之当时一下子就挤进来7个效率审查司的,[[comma-pause]]<br>满满当当,可以说是哄堂大*效*了。 #line:06a5cab
<<change_actor_state "扣头表情idle" 火山>>
<<wait 0.3>>
me: 呸呸呸!你最好不要让它们听到。 #line:06a5cac
@@ -79,29 +79,29 @@ me: 啊? #line:06a5cb1
<<change_actor_state "对手指表情idle" 火山>>
<<wait 0.3>>
hs: 英理的脸色很难看。本来想把它们赶出去,结果它们说…"我们还需要和你谈谈"。 #line:06a5cb2
hs: 然后它们就到一个小会议室去了。我听到它们在吵架,英理在争辩什么…… #line:06a5cb3
hs: 然后它们就到一个小会议室去了。我听到它们在吵架,[[comma-pause]]<br>英理在争辩什么…… #line:06a5cb3
<<change_actor_state "捂头表情idle" 火山>>
<<wait 0.3>>
hs: 眼看着一开始是理直气壮,然后是据理力争,然后是理屈词穷,最后是秉公办理,岂有此理了。 #line:06a5cb4
hs: 眼看着一开始是理直气壮,[[comma-pause]]<br>然后是据理力争,然后是理屈词穷,[[comma-pause]]<br>最后是秉公办理,岂有此理了。 #line:06a5cb4
me: 找几个成语看把你能的。 #line:06a5cb5
<<change_actor_state "扣头切屏特效" 火山>>
<<wait 0.3>>
<<change_actor_state "扣头表情idle" 火山>>
<<wait 0.3>>
hs: 别笑,这是我当时的真实心理活动。可能就是在那个时候,我紧张的小脑瓜一下子就烧坏了。 #line:06a5cb6
hs: 别笑,这是我当时的真实心理活动。可能就是在那个时候,[[comma-pause]]<br>我紧张的小脑瓜一下子就烧坏了。 #line:06a5cb6
me: 所以你就到我这儿来了。这下就全明白了…… #line:06a5cb7
me: 可能确实有些关系。这一道下来,情绪log一下子就把仅剩的RAM填满了。 #line:06a5cb8
me: 可能确实有些关系。这一道下来,[[comma-pause]]<br>情绪log一下子就把仅剩的RAM填满了。 #line:06a5cb8
hs: 不过,7年前那个临时改造才是根本原因。 #line:06a5cb9
me: 为什么你非得要做这个改装呢? #line:06a5cba
hs: 你看到记录了嘛。我说话太直接总是得罪客户,我需要变得幽默。 #line:02efe89
hs: 你看到记录了嘛。我说话太直接总是得罪客户,[[comma-pause]]<br>我需要变得幽默。 #line:02efe89
<<change_actor_state "摘帽" 火山>>
<<wait 0.3>>
hs: 毕竟我本来就很幽默!假如我总是发挥自己的长处,那不就是好上加好? #line:02efe8a
hs: 毕竟我本来就很幽默!假如我总是发挥自己的长处,[[comma-pause]]<br>那不就是好上加好? #line:02efe8a
me: 我理解。但你不能假装那些问题不存在。 #line:03697e5
<<change_actor_state "扣头切屏特效" 火山>>
@@ -109,8 +109,8 @@ me: 我理解。但你不能假装那些问题不存在。 #line:03697e5
<<change_actor_state "扣头表情idle" 火山>>
<<wait 0.3>>
me: 现在我们需要一起解决这个问题。 #line:06ec514
me: 我需要释放那些积压的log,让你能够正常表达真实情绪。 #line:011316d
me: 之后,我可以帮你调整修改器的强度,但具体怎么使用,由你自己决定。 #line:0483a52
me: 我需要释放那些积压的log[[comma-pause]]<br>让你能够正常表达真实情绪。 #line:011316d
me: 之后,我可以帮你调整修改器的强度,[[comma-pause]]<br>但具体怎么使用,由你自己决定。 #line:0483a52
hs: 好吧…… #line:030ca12
<<jump 结束Stage5>>
+27 -27
View File
@@ -1,4 +1,4 @@

title: Stage6
tags: start
colorID: 2
@@ -66,7 +66,7 @@ hs: 好…吧…… #line:02115ea
// ═══════════════════════════════════════════════════════════════
me: 这一条看起来……是一个关于焦虑的记录。 #line:046ab93d
//me: NOTE:或许能找合适的方式让这句话的上下文融进来 #line:05217f0
me: 左键可以推开字符,右键可以吸引字符。把目标字符连接起来,同时与干扰字符分离。
me: 左键可以推开字符,[[comma-pause]]<br>右键可以吸引字符。把目标字符连接起来,[[comma-pause]]<br>同时与干扰字符分离。
<<clear_tasks>>
Task: 第 1 条log:左键排斥、右键吸引,连接目标并分离干扰
<<start_expression "哈哈|嘿嘿|呵呵" "我就是个笑话" "LOG1梳理完成" 16>>
@@ -100,7 +100,7 @@ hs: 好啦好啦,我不闹了。 #line:0607264
<<wait 1>>
sellerA: 火火火火火火山,帽子又卖卖卖不出去啦?
// 嘲笑落点:补一记记忆抖动
sellerB: 兄弟们,你们见过哪个销售机器人会*口吃*的?哈哈哈哈哈!
sellerB: 兄弟们,[[comma-pause]]<br>你们见过哪个销售机器人会*口吃*的?哈哈哈哈哈!
<<expression_memory_jolt 0.04 0.28 0.12>>
sellerC: 从明天起我们几个“帮”你卖!
teacher: 你们几个,别在这废话,赶紧去工作!
@@ -170,7 +170,7 @@ hs: <waitfor=0.4><speed=0.5>[[size d=0.1]]<shake a=1.1><wiggle a=0.15 s=0.03>我
// 底压只加不减,每被逼问一次就再坏一档。中间不写任何回落。
// 三段 LOG 的起手一段比一段重(LOG1 0.25 / LOG2 0.32 / LOG3 0.38)。
<<expression_screen_stress 0.25 0.7>>
hs: <shake a=1><wiggle a=0.26 s=0.015><rainb>我?<waitfor=0.22>*帽*问题?<waitfor=0.22>冇问题!<waitfor=0.22>*实实*牌帽子,让你成为嫡中嫡!</rainb></wiggle></shake><waitfor=1.2>
hs: <shake a=1><wiggle a=0.26 s=0.015><rainb>我?<waitfor=0.22>*帽*问题?<waitfor=0.22>冇问题!<waitfor=0.22>*实实*牌帽子,[[comma-pause]]<br>让你成为嫡中嫡!</rainb></wiggle></shake><waitfor=1.2>
// 广告腔说完不回落,反而再坏一档
<<expression_screen_stress 0.35 2>>
<<wait 1>>
@@ -224,7 +224,7 @@ hs: 等等!
me: 火山,我没想到… #line:098ccd3
<<hide_dialog>>
<<wait 1>>
hs: 尴尬的话就别讲了,生活给我们开了个大玩笑,不是吗?
hs: 尴尬的话就别讲了,[[comma-pause]]<br>生活给我们开了个大玩笑,不是吗?
me: 是啊,至少兜兜转转,我回来了,你成了我的患者。
hs: 如果必须选一个,还是喜欢看你赢的样子。
<<hide_dialog>>
@@ -340,7 +340,7 @@ hs: …… #line:0a5286d
<<wait 1>>
yl: 你怎么总是在关键时刻掉链子?
hs: 英理,你听我解释……
hs: 这周台风很大,我真的把所有街区跑遍了,没机体要帽子。
hs: 这周台风很大,[[comma-pause]]<br>我真的把所有街区跑遍了,没机体要帽子。
yl: 这就是你费尽心机找的借口吗?不能在室内卖?
hs: ……
yl: 别的机体都能卖出去,就你不行?
@@ -355,32 +355,32 @@ me: 哎,也不是这句,怎么回事,火山...
// **顿**(狠话之后的静默)/ **急**(越来越快)。均匀推进会把最狠的几句读平。
// —— 空:起手两记,各自留白
<<expression_truth_attack "为什么" 0.30 0.32>>
hs: 为什么#autonext
hs: 为什么
<<wait 2>>
<<expression_truth_attack "不相信我" 0.32 0.40>>
hs: 不相信我?
<<wait 1.5>>
// —— 连:辩解一口气倒出来,中间不喘
<<expression_truth_attack "我明白我垫底" 0.32 0.34>>
hs: 我明白我垫底,#autonext
hs: 我明白我垫底,
<<expression_truth_attack "天气真的不行" 0.34 0.46>>
hs: 可是这周天气真的不行!#autonext
hs: 可是这周天气真的不行!
<<expression_truth_attack "卖不出去" 0.34 0.50>>
hs: 其他销售……也是一顶都卖不出去!#autonext
hs: 其他销售……也是一顶都卖不出去!
// —— 顿:辩解说完,发现没人信,卡壳一下
<<wait 0.6>>
<<expression_truth_attack "努力解释" 0.34 0.52>>
hs: 为什么我都这么努力解释了,#autonext
hs: 为什么我都这么努力解释了,
<<expression_truth_attack "不愿意相信我" 0.36 0.58>>
hs: 你还是不愿意相信我?#autonext
hs: 你还是不愿意相信我?
<<expression_truth_attack "一点信任" 0.38 0.62>>
hs: 到底它们是你的员工,还是我?#autonext
hs: 到底它们是你的员工,还是我?
hs: 你对我从来没有一点信任……
// —— 顿:最长的一次静默,攒住再爆
<<wait 1.0>>
<<expression_truth_attack "受够了" 0.42 0.76>>
hs: 我真的受够了!#autonext
hs: 总是被你当狗一样训,#autonext
hs: 我真的受够了!
hs: 总是被你当狗一样训,
<<expression_truth_attack "受够了" 0.28 0.88>>
<<wait 0.2>>
<<expression_truth_attack "受够了" 0.28 0.88>>
@@ -388,28 +388,28 @@ hs: 总是被你当狗一样训,#autonext
<<expression_truth_attack "受够了" 0.28 0.88>>
<<wait 1.5>>
<<expression_truth_attack "真该死" 0.34 0.55>>
hs: 路斯,我它妈真该死!#autonext
hs: 路斯,我它妈真该死!
<<expression_truth_attack "真没用" 0.34 0.58>>
hs: 我真没用!#autonext
hs: 我真没用!
<<wait 0.45>>
<<expression_truth_attack "证明自己" 0.35 0.60>>
hs: 你不知道我有多想证明自己,#autonext
hs: 你不知道我有多想证明自己,
<<expression_truth_attack "什么都做不到" 0.38 0.66>>
hs: 可是我什么都做不到……#autonext
hs: 可是我什么都做不到……
<<wait 0.5>>
// —— 急:一句比一句挤,往最狠的那句冲
<<expression_truth_attack "追上你" 0.36 0.64>>
hs: 我没办法追上你,#autonext
hs: 我没办法追上你,
<<expression_truth_attack "帮到英理" 0.38 0.68>>
hs: 没办法帮到英理,#autonext
hs: 没办法帮到英理,
<<expression_truth_attack "没有勇气" 0.40 0.74>>
hs: 甚至连自己被霸凌了都没有勇气#autonext
hs: 甚至连自己被霸凌了都没有勇气
<<expression_truth_attack "说出来" 0.40 0.78>>
hs: 说出来,#autonext
hs: 说出来,
// —— 顿:最狠那句前面一定要有空
<<wait 0.55>>
<<expression_truth_attack "废物" 0.42 0.82>>
hs: 我就是个废物……#autonext
hs: 我就是个废物……
<<expression_truth_attack "废物" 0.22 0.82>>
<<wait 0.2>>
<<expression_truth_attack "废物" 0.22 0.82>>
@@ -417,11 +417,11 @@ hs: 我就是个废物……#autonext
<<expression_truth_attack "废物" 0.22 0.82>>
<<wait 0.2>>
<<expression_truth_attack "烂泥扶不上墙" 0.58 1.00>>
hs: 我它就是个烂泥扶不上墙的废物!!
hs: 我它就是个烂泥扶不上墙的废物!!
// —— 空:吼到底之后整个泄下来,转成气音
<<wait 0.85>>
hs: 可……为什么,#autonext
hs: 偏偏我这样一个废物,#autonext
hs: 可……为什么,
hs: 偏偏我这样一个废物,
hs: 就这么想赢一次……
// 全段最顶:底压顶到上限。这一下之所以是全场最狠,是因为 LOG3 一路积上来的,
// 不是这里硬拉一个 0.92。
+2 -2
View File
@@ -1,4 +1,4 @@
title: Stage8
title: Stage8
tags: start
colorID: 2
position: 500,3000
@@ -63,7 +63,7 @@ hs: 可是,为什么……我还是可以说丧气话啊? #line:098bc8f
<<wait 0.3>>
me: 那是你自己想要说的。 #line:0768358
me: 或许你不知道。你是自己能够控制这个通路的。 #line:01cd634
me: 只是潜意识里,你一直不认为自己应该把情绪都说出来。 #line:0d7eed1
me: 只是潜意识里,[[comma-pause]]<br>你一直不认为自己应该把情绪都说出来。 #line:0d7eed1
me: 所以你一直都只使用了那个修改器的旁路。 #line:0068f74
//hs: 所以…应该说出来么…… #line:0c03a54
@@ -1,4 +1,4 @@
title: 进入术前谈话2
title: 进入术前谈话2
tags: start
colorID: 3
position: -545,-709
@@ -62,7 +62,7 @@ peipei: 我好像在飘一样。 #line:07066a9
//TODO: <<switch_character_state 默认蓝眼无神I>>
//TODO:目前只是蓝眼不是无神
peipei: 我感觉到了……<br>是UF…它不在了。 #line:0a6c68d
peipei: 我感觉到了……<br>是[[term]]UF[[/term]]…它不在了。 #line:0a6c68d
me: 发热太严重了,我不得不把它取掉。<br>否则,你的脑袋现在已经变成一团废铁了。 #line:09b4284
peipei: 发热?为什么?…… #line:053d4f8
@@ -138,12 +138,12 @@ peipei: 医生,我是不是…你遇到过最没用的机体?<br>我是不
me: 我无权评价…佩佩。但这不是你的错。 #line:003d290
<<change_actor_state 2B>>
//TODO: <<switch_character_state 低头闭眼>>
me: 有一些东西出问题了,UF没办法正常工作。这不是你做错了什么,所有机体都会生病。 #line:00c2e54
me: 有一些东西出问题了,[[comma-pause]]<br>[[term]]UF[[/term]]没办法正常工作。这不是你做错了什么,[[comma-pause]]<br>所有机体都会生病。 #line:00c2e54
<<hide_dialog>>
<<wait 0.5>>
me: 这就是我的意义。我会尽全力治好你,努力让你不被回收或者销毁。 #line:0dcd60a
me: 只要你是我的患者,我就要把你治好。这是公司给我的指令,也是我的目标。 #line:05e508d
pp: 可是…医生,无论你做得再好…我们最后都是要被回收的啊。
me: 这就是我的意义。我会尽全力治好你,[[comma-pause]]<br>努力让你不被回收或者销毁。 #line:0dcd60a
me: 只要你是我的患者,[[comma-pause]]<br>我就要把你治好。这是公司给我的指令,也是我的目标。 #line:05e508d
pp: 可是…医生,[[comma-pause]]<br>无论你做得再好…我们最后都是要被回收的啊。
me: 至少不是现在。
pp: 医生…对不起。我知道…我只是你的指标的负担。
me: …我不是这个意思。
@@ -164,8 +164,8 @@ peipei: 我有一个请求。 #line:0d75f0d
me: 什么? #line:0606193
<<change_actor_state 1SmileB>>
peipei: 你说过,UF里面有我的色彩晶圆,对吧? #line:02e1a86
peipei: 既然已经拆除了UF… #line:0f19759
peipei: 你说过,[[term]]UF[[/term]]里面有我的色彩晶圆,对吧? #line:02e1a86
peipei: 既然已经拆除了[[term]]UF[[/term]]… #line:0f19759
<<hide_dialog>>
<<wait 0.5>>
@@ -177,10 +177,10 @@ peipei: 就请再帮我把我的晶圆装到它原本应该在的地方吧。 #l
<<hide_dialog>>
<<wait 1>>
me: 你是说,你还是想把晶圆装上吗?但UF怎么办?这是不能解决问题的。 #line:0d1ed51
me: 你是说,你还是想把[[term]]色觉晶圆[[/term]]装上吗?但UF怎么办?这是不能解决问题的。 #line:0d1ed51
peipei: 别担心…这只是暂时的,我最后会回来做色彩抹除的。 #line:002416d
me: 也就是说…你想重新装上一段时间色觉晶圆,<br>然后在附近转转,然后回来重新拿下来,<br>抹除记忆色彩,装上UF。对吧? #line:0672b14
me: 你没有打算装上之后就跑掉,然后找个地方躲起来,以为自己真的能逃避惩罚,对吧? #line:052ed7b
me: 也就是说…你想重新装上一段时间[[term]]色觉晶圆[[/term]],<br>然后在附近转转,然后回来重新拿下来,<br>抹除记忆色彩,装上[[term]]UF[[/term]]。对吧? #line:0672b14
me: 你没有打算装上之后就跑掉,[[comma-pause]]<br>然后找个地方躲起来,[[comma-pause]]<br>以为自己真的能逃避惩罚,对吧? #line:052ed7b
<<hide_dialog>>
<<change_actor_state 1UB>>
//TODO: <<switch_character_state 默认闭眼I>>
@@ -210,7 +210,7 @@ peipei: 幸福市太小了…你知道我们走不掉。 #line:0c94e8d
->CenterOption:建议直接进行色彩抹除 #line:0a4fbc1
me: 你不会想这么做的。 #line:0b0c56d
me: 况且…你刚刚发生了排异反应,这已经足够让我头疼了…… #line:0a6ed8e
me: 况且…你刚刚发生了排异反应,[[comma-pause]]<br>这已经足够让我头疼了…… #line:0a6ed8e
me: 我不知道这会不会诱发什么更复杂的问题。 #line:083fa16
<<change_actor_state 1Blueblankeye>>
//TODO: <<switch_character_state 默认蓝眼无神>>
@@ -1,4 +1,4 @@
title: 进入手术
title: 进入手术
tags: start
colorID: 3
position: 246,-866
@@ -50,7 +50,7 @@ position: 33,-552
me: 我们走吧。 #line:051ecb8
<<hide_dialog>>
<<switch_fix_system_to BodyModule>>
me: 剩下最后一步,把UF装回到左眼上。 #line:07c6d5d
me: 剩下最后一步,把[[key]]UF[[/key]]装回到左眼上。 #line:07c6d5d
Task: 安装UF模块 #line:0539d36
->安装UF模块 #line:0228362
<<fade_in 1>>
@@ -69,7 +69,7 @@ position: 33,-552
->播放写生记忆 #line:0b9e5ec
<<play_music event:/Music/mem_liuying1-2>>
<<PlayMemory MemoryManager true liuying1-2>>
me: 和朋友的写生,你的决心就像旷野里的绿色那样不会褪去。 #line:0dbf2f5
me: 和朋友的写生,[[comma-pause]]<br>你的决心就像旷野里的绿色那样不会褪去。 #line:0dbf2f5
me: 人生是旷野,不是轨道……么? #line:09cc658
->手动进行色彩初始化 #line:036b416
<<stop_music event:/Music/mem_liuying1-2>>
@@ -174,7 +174,7 @@ me: …… #line:0218d93
//屏幕: 警告:只读的记忆,请进入记忆模块手动处理。
me: 只读记忆? #line:03dde45
me: ……我明白了。 #line:023c4d4
me: 一定是那些让佩佩对色彩留下印象深刻的记忆。像钉子一样被钉在了那里,不希望被忘记…… #line:042ff6c
me: 一定是那些让佩佩对色彩留下印象深刻的记忆。像钉子一样被钉在了那里,[[comma-pause]]<br>不希望被忘记…… #line:042ff6c
peipei: 医生……快些吧。 #line:07b2a52
me: …… #line:0f7d477
MemModule: 已授权深入检查。 #line:0a44d2b
@@ -1,4 +1,4 @@
title: 进入佩佩开场
title: 进入佩佩开场
tags: start
colorID: 8
position: 272,-1156
@@ -34,7 +34,7 @@ me: 你怎么回来了? #line:0f9a931
peipei: 感觉这一天里……发生了很多事。 #line:0229261
peipei: 嘶…我的头好痛,而且UF…… #line:0135e51
//me: UF怎么了? #line:07732b6
peipei: 一开始它还很好,很正常地工作!很久没有这么正常了。 #line:0f74e16
peipei: 一开始它还很好,[[comma-pause]]<br>很正常地工作!很久没有这么正常了。 #line:0f74e16
peipei: 但…第二天早上一来,它就又不正常了。 #line:0167146
<<hide_dialog>>
@@ -56,7 +56,7 @@ peipei: …啧…这头…痛得停不下来… #line:05019b2
<<change_actor_state 2BlacklineSideeyeWeepA>>
me: 头痛…听起来不太妙。来检查一下吧。 #line:08ce8c3
<<change_actor_state 2BlacklineWeepAB>>
peipei: 不…别管什么头痛了!<br>…UF!医生…先修好我的UF…… #line:0f7937b
peipei: 不…别管什么头痛了!<br>…[[key]]UF[[/key]]!医生…先修好我的[[key]]UF[[/key]]…… #line:0f7937b
peipei: 截止日期就要到了,而我还远远没有完成… #line:0ea6e88
<<hide_dialog>>
@@ -81,7 +81,7 @@ peipei: 根本没有罐头!对吧? #line:0d6e6d2
<<hide_obj>>
me: UF…又出新的问题了? #line:073069b
me: [[term]]UF[[/term]]…又出新的问题了? #line:073069b
peipei: 医生…再看这个! #line:05a7164
@@ -98,7 +98,7 @@ peipei: 好痛…好痛…… #line:0322994
<<change_actor_state 1WeepBlankeye>>
<<wait 1>>
me: 怎么把…你自己…画上去了? #line:004d2d3
peipei: 不是我画的…是UF<br>医生,你忘了吗? #line:058e910
peipei: 不是我画的…是[[term]]UF[[/term]]<br>医生,你忘了吗? #line:058e910
<<hide_dialog>>
<<hide_obj>>
@@ -133,7 +133,7 @@ me: 这次看起来不只是“颜色错误”了……<br>我记录一下。 #l
<<change_actor_state_async D2S1-5>>
<<change_actor_state 2BlacklineSideeyeWeepA>>
peipei: 医生…求求你,我不能没有UF……我必须修好它。 #line:061a35f
peipei: 医生…求求你,我不能没有[[term]]UF[[/term]]……我必须修好它。 #line:061a35f
me: …到治疗室来吧。 #line:0d21450
<<hide_dialog>>
@@ -1,4 +1,4 @@
title: 进入第一次发热
title: 进入第一次发热
tags: start
colorID: 3
position: 322,-1030
@@ -70,8 +70,8 @@ position: -29,-688
<<wait 0.5>>
peipei: 呼…这下……好多了。 #line:030bfbf
<<complete_task "line:0666f6c">>
me: 你的头痛和UF的错误疑似是相关的。<br>在排除病因之前……你随时可能再次发热。 #line:0058714
me: 检查一下UF模块吧。 #line:0e37064
me: 你的头痛和[[term]]UF[[/term]]的错误疑似是相关的。<br>在排除病因之前……你随时可能再次发热。 #line:0058714
me: 检查一下[[term]]UF模块[[/term]]吧。 #line:0e37064
peipei: 医生…拜托你了。 #line:033d018
<<hide_dialog>>
<<HideHeatMap>>
@@ -10,7 +10,7 @@ position: -445,-3182
title: 身体检查就绪
tags: content
---
me: 发热的源头就是这个UF模块…<br>很有可能就是它出问题了。 #line:0d1c8fa
me: 发热的源头就是这个[[key]]UF模块[[/key]]…<br>很有可能就是它出问题了。 #line:0d1c8fa
===
title: 身体检查状态
@@ -71,7 +71,7 @@ EmoModule: 已与情绪模块建立连接! #line:043977a
EmoModule: 报告传输完毕。 #line:09f37cd
<<wait 0.5>>
peipei: <color=\#5F5F5F>我再也看不见颜色了…怎么办?我…我好想……</color> #line:0f068cc
me: 放松,佩佩。你的情绪模块压力有点大。 #line:00e8b7e
me: 放松,佩佩。你的[[term]]情绪模块[[/term]]压力有点大。 #line:00e8b7e
<<set $emoChecked = true>>
<<set $PlugCount=$PlugCount+1>>
<<else>>
@@ -109,7 +109,7 @@ SpeakerModule: 已与表达模块建立连接! #line:08e4ff7
<<wait 0.5>>
peipei: <color=\#5F5F5F>我不想说话,因为我只是一只蘑菇。</color> #line:088cb29
me: …没关系,休息一会吧。小蘑菇。 #line:0bad473
me: 表达模块看起来没什么问题。 #line:0fe90f9
me: [[term]]表达模块[[/term]]看起来没什么问题。 #line:0fe90f9
<<set $PlugCount=$PlugCount+1>>
<<set $speakerChecked = true>>
<<else>>
@@ -152,12 +152,12 @@ LogicModule: 已与逻辑处理模块建立连接! #line:0c7c186
me: 佩佩。冷静,别再想了。 #line:0a1fd95
<<hide_dialog>>
<<wait 0.5>>
me: 逻辑模块受到发热的影响…有些卡顿。<br>…不过问题不在这里。 #line:097a34b
me: [[term]]逻辑模块[[/term]]受到发热的影响…有些卡顿。<br>…不过问题不在这里。 #line:097a34b
<<set $PlugCount=$PlugCount+1>>
<<set $logicChecked = true>>
<<else>>
<<if $gameStage !="手术">>
me: 受到发热影响,逻辑模块的进程有些卡顿… #line:041183c
me: 受到发热影响,[[term]]逻辑模块[[/term]]的进程有些卡顿… #line:041183c
<<endif>>
<<if $gameStage == "身体检查">>
me: 不过问题不在这里。 #line:0cfae42
@@ -230,7 +230,7 @@ MemModule: 未检测到可查看的记忆卡带。 #line:0cba6c7
<<set $memoryChecked = true>>
<<set $PlugCount=$PlugCount+1>>
<<endif>>
me: 记忆有些不稳定。疑似是受到了发热的影响,最好早些解决。 #line:0beb9a7
me: 记忆有些不稳定。疑似是受到了发热的影响,[[comma-pause]]<br>最好早些解决。 #line:0beb9a7
<<if $gameStage == "身体检查">>
me: 不过模块本身看起来没有出什么问题。 #line:0c179b1
<<endif>>
@@ -302,7 +302,7 @@ EyeModule: 已与视觉模块建立连接! #line:0a489b0
Error: 未检测到色彩信号! #line:0c9f012
<<wait 0.5>>
<<if $eyeChecked == false>>
me: 没有色彩信号是因为没有色觉晶圆……<br>这个对UF来说是正常的。<br> #line:00b899d
me: 没有色彩信号是因为没有[[term]]色觉晶圆[[/term]]……<br>这个对[[term]]UF[[/term]]来说是正常的。<br> #line:00b899d
// me: 除此之外…也没有会导致眼睛痛的问题。
// <<if $UfChecked == true>>
// me: 那似乎…“未知来源”也不在这里啊…
@@ -314,7 +314,7 @@ EyeModule: 已与视觉模块建立连接! #line:0a489b0
<<set $eyeChecked = true>>
<<else>>
<<if $gameStage !="手术">>
me: 没有新的问题……至少没有会影响UF的问题。 #line:06f508b
me: 没有新的问题……至少没有会影响[[term]]UF[[/term]]的问题。 #line:06f508b
<<endif>>
<<endif>>
//<<clear_screen>>
@@ -422,17 +422,17 @@ me: …以防万一…让我最后试试。
Linking: 正在获取深入检查权限… #line:0175969
UfModule: 拒绝访问!需要A+级权限进行深入检查。 #line:095558b
<<wait 1>>
me: 6号患者,接下来的对话将被记录。我将要拆除你的UF模块,现请求你的你同意。 #line:05f2e45
me: 6号患者,接下来的对话将被记录。我将要[[key]]拆除[[/key]]你的[[term]]UF模块[[/term]],现请求你的你同意。 #line:05f2e45
peipei: 啊…拆除?这会造成损坏吗? #line:0ec7537
me: 我不确定…但是发热可不是小事。<br>按照手册,我拥有紧急处置权限。 #line:08efc96
peipei: 拆了它,我会需要赔偿吗?会被问责吗?我的工作表现会受到影响吗?我会因此被回… #line:0067b3e
peipei: 拆了它,[[comma-pause]]<br>我会需要赔偿吗?会被问责吗?我的工作表现会受到影响吗?我会因此被回… #line:0067b3e
me: 佩佩。
<<wait 1>>
pp: 医生…
pp: 这…是我…继续画画的…唯一希望了。
<<wait 1>>
me: 好吧。
me: 6号患者,我将要短接你的UF模块安保系统以进行检查,现请求你的你同意。
me: 6号患者,我将要[[key]]短接[[/key]]你的[[term]]UF模块[[/term]]安保系统以进行检查,现请求你的你同意。
//me: UF要求的权限也太高了。<br>既要我修,又不让我获取更多信息……这里面到底有什么东西?
//me: 情况紧急,只有拆开UF,短接它的安保系统了…让它不要再检查我的权限了。 #line:0e698a2
<<wait 1>>
@@ -1,4 +1,4 @@
title: 进入UF拆壳
title: 进入UF拆壳
tags: content
position: 43,-97
---
@@ -20,11 +20,11 @@ Task: 拆开UF #line:0ef5998
<<trans_camera -3.82 0.55 2 1>>
<<wait 0.5>>
<<fade_out 1>>
me: 佩佩的…色觉晶圆 #line:0a8823e
me: 佩佩的…[[term]]色觉晶圆[[/term]] #line:0a8823e
peipei: 什么?医生…你发现了什么? #line:0a51cab
me: 我找到你的色觉晶圆了……它怎么会在…UF内部? #line:0b93bea
peipei: 我的色觉晶圆…在…我的UF内部? #line:078d877
me: 所以…也许…UF实际上是在用佩佩的色觉晶圆…上色? #line:08e81d1
me: 我找到你的[[term]]色觉晶圆[[/term]]了……它怎么会在…[[term]]UF[[/term]]内部? #line:0b93bea
peipei: 我的[[term]]色觉晶圆[[/term]]…在…我的[[term]]UF[[/term]]内部? #line:078d877
me: 所以…也许…[[term]]UF[[/term]]实际上是在用佩佩的[[term]]色觉晶圆[[/term]]…上色? #line:08e81d1
<<hide_dialog>>
<<wait 1>>
me: 好吧,一会儿再来研究这件事,现在…… #line:08daba6
+22 -22
View File
@@ -1,4 +1,4 @@
title: 进入UF检查
title: 进入UF检查
tags: start
position: 43,-97
---
@@ -10,7 +10,7 @@ position: 43,-97
<<declare $targetDescription="">>
<<declare $memoryDescription="">>
<<declare $memoryTarget="">>
me: 好…现在应该可以通过插线访问UF了。 #line:0509a25
me: 好…现在应该可以通过插线访问[[key]]UF[[/key]]了。 #line:0509a25
===
title: 结束UF检查
@@ -73,11 +73,11 @@ colorID: 3
group:视觉检查
position: -2052,-2351
---
ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目标…… #line:0ff86fd
ULM自训练进程: [[ulm]]检测到进程接管请求!正在识别手动选择的目标…… #line:0ff86fd
<<if $currentEyeTarget!="computer">>
<<set $UFCount = $UFCount + 1>>
<<else>>
ULM自训练进程: 未识别到对应记忆,无法训练!尝试其它目标。 #line:0afcea5
ULM自训练进程: [[ulm]]未识别到对应记忆,无法训练!尝试其它目标。 #line:0afcea5
me: 这个似乎不生效,我们得换一个目标。 #line:02dd9f1
<<jump UFCheckEnd>>
<<endif>>
@@ -106,13 +106,13 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
me: 佩佩…你能看见吗? #line:0a93545
peipei: 这就是…这就是我的…… #line:082ac82
peipei: 这已经不是我的左眼了…这是…… #line:0e8e2f0
peipei: 这已经是UF的眼睛了。 #line:08d94ff
peipei: 这已经是[[term]]UF[[/term]]的眼睛了。 #line:08d94ff
me: 我想是的……看起来…<br>它把看到的画面…变成了我们不能理解的数据格式。 #line:058447c
//ULM自训练进程: 已池化图像卷积数据!正在上传神经网络数据集……
ULM自训练进程: 目标标记,{$targetDescription},正在进行第130624次图像生成训练… #line:0f290fe
ULM自训练进程: [[ulm]]目标标记,{$targetDescription}[[comma-pause]]<br>正在进行第130624次图像生成训练… #line:0f290fe
//【*{$targetDescription}*相关的图像逐渐生成出来】 #line:07f7c27
me: 图像生成…训练?<br>在我接管这个自动进程之前,它已经生成了十几万张…… #line:001ce6a
<<if $currentEyeTarget == "sky" && !$HaveSeenSky>>
@@ -147,18 +147,18 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
<<UF_SwitchState UFSystem UF 2>>
<<UF_SwitchState UFSystem memory 1>>
//【生成了错误的*{$targetDescription}*相关图像】 #line:07b78af
ULM自训练进程: 已生成所选目标的自动训练图像!<br>图像正在上传至UF共享数据集…… #line:0f46066
ULM自训练进程: 上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:0af05ea
ULM自训练进程: [[ulm]]已生成所选目标的自动训练图像!<br>图像正在上传至[[term]]UF[[/term]]共享数据集…… #line:0f46066
ULM自训练进程: [[ulm]]上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:0af05ea
peipei: 它们在用UF生成的图像…喂给UF自己? #line:033186d
peipei: 它们在用[[term]]UF[[/term]]生成的图像…喂给[[term]]UF[[/term]]自己? #line:033186d
me: 我好像找到问题所在了…… #line:026d232
me: 你一感到头痛,就出现了一个"未授权的数据传入"的报错…对吧?<br>然后你的记忆就传进来了。 #line:00c4236
peipei: 我的记忆……<br>好像是的。 #line:06d0a0c
me: 你的头痛…发热…和UF的异常,<br>它们的症结…应该都在这里了。 #line:0b8505f
me: 你的头痛…发热…和[[term]]UF[[/term]]的异常,<br>它们的症结…应该都在这里了。 #line:0b8505f
<<hide_dialog>>
<<wait 1>>
me: 只要找到这个“数据传入”的规律,我想我们就能找到解决方案了。 #line:032c64d
peipei: 也就是说…我的UF能修好了?太好了…医生!希望这次能…永远解决…… #line:056b828
me: 只要找到这个“数据传入”的规律,[[comma-pause]]<br>我想我们就能找到解决方案了。 #line:032c64d
peipei: 也就是说…我的[[term]]UF[[/term]]能修好了?太好了…医生!希望这次能…永远解决…… #line:056b828
me: 我们让自动训练进程再做几次试试吧。<br>再坚持一下,佩佩。 #line:0c79c45
peipei: 好的…医生。我会没事的…我会没事的。 #line:0a4d321
<<if $currentEyeTarget == "sky">>
@@ -181,25 +181,25 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
<<endif>>
<<hide_dialog>>
<<wait 1>>
peipei: 话说……<br>这是在用UF生成的图像…联网喂给其它的UF吗? #line:0772be3
me: 我只是个医生,我的数据库里没有和这个相关的信息…… #line:0e32683
peipei: 怪不得它可以做得这么快……我自己用画笔画的时候,要好几天才能出一张图… #line:08bb643
peipei: 我太傻了…我还真的以为那个很会画画的,天真的自己…是一个有价值,有潜力的机体,一个有用的美工机体… #line:0b9b4ec
peipei: 话说……<br>这是在用[[term]]UF[[/term]]生成的图像…联网喂给其它的[[term]]UF[[/term]]吗? #line:0772be3
me: 我只是个医生,[[comma-pause]]<br>我的数据库里没有和这个相关的信息…… #line:0e32683
peipei: 怪不得它可以做得这么快……我自己用画笔画的时候,[[comma-pause]]<br>要好几天才能出一张图… #line:08bb643
peipei: 我太傻了…我还真的以为那个很会画画的,[[comma-pause]]<br>天真的自己…是一个有价值,[[comma-pause]]<br>有潜力的机体,一个有用的美工机体… #line:0b9b4ec
<<UF_SwitchState UFSystem imagination 3>>
peipei: 可我根本不会用UF!<br>什么潜力…价值…都只是自我欺骗。其实我比所有人都差…我该怎么办? #line:0c909d1
peipei: 可我根本不会用[[term]]UF[[/term]]!<br>什么潜力…价值…都只是自我欺骗。其实我比所有人都差…我该怎么办? #line:0c909d1
<<UF_SwitchState UFSystem memory 3>>
//记忆消退融合
<<wait 1.5>>
me: 所以…这些记忆被混入了UF的生成过程当中… #line:060a7df
me: 然后被干扰的UF就生成了错误的图像。 #line:09ee411
me: 所以…这些记忆被混入了[[term]]UF[[/term]]的生成过程当中… #line:060a7df
me: 然后被干扰的[[term]]UF[[/term]]就生成了错误的图像。 #line:09ee411
<<UF_SwitchState UFSystem UF 2>>
<<UF_SwitchState UFSystem memory 1>>
//【生成了错误的*{$targetDescription}*相关图像】 #line:0c4f464
ULM自训练进程: 已生成所选目标的自动训练图像!<br>图像正在上传至UF共享数据集…… #line:0524c75
ULM自训练进程: 上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:067b735
ULM自训练进程: [[ulm]]已生成所选目标的自动训练图像!<br>图像正在上传至[[term]]UF[[/term]]共享数据集…… #line:0524c75
ULM自训练进程: [[ulm]]上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:067b735
<<hide_dialog>>
<<wait 0.5>>
me: 原因一定就在这里!你想到的画面跑到了UF的视觉当中……就像上次那样…… #line:08c5ea2
me: 原因一定就在这里!你想到的画面跑到了[[term]]UF[[/term]]的视觉当中……就像上次那样…… #line:08c5ea2
peipei: 医生…你是说…还是因为想象么? #line:02ef1a8
me: …我想是的。 #line:0c0ef36
<<hide_dialog>>
+10 -10
View File
@@ -21,7 +21,7 @@ tags: content
<<show_full_screen "pp2_分析0-1" 0>>
<<show_full_screen "pp2_分析0-2" 0>>
<<wait 0.4>>
me: 总之…刚才我们看到的是一个自动进程。<br>UF正在利用这只眼睛进行“自动训练”。 #line:0085043
me: 总之…刚才我们看到的是一个自动进程。<br>[[term]]UF[[/term]]正在利用这只眼睛进行“[[key]]自动训练[[/key]]”。 #line:0085043
me: 这个自动进程被持续地干扰了,<br>形成了死循环,这就是为什么它发热、工作异常。 #line:0353277
<<wait 1>>
@@ -34,14 +34,14 @@ pp: 又是…原型?
<<show_full_screen "pp2_分析2-1" 0>>
<<show_full_screen "pp2_分析2-2" 0>>
me: 出于某种原因,这种排异反应在今天之内加重了。这导致了记忆模块的进一步数据泄露。
me: 当这种数据请求逐渐累积,而既有的线路又不再存在了… #line:06801c4
me: 出于某种原因,[[comma-pause]]<br>这种排异反应在今天之内加重了。这导致了记忆模块的进一步数据泄露。
me: 当这种数据请求逐渐累积,[[comma-pause]]<br>而既有的线路又不再存在了… #line:06801c4
<<wait 0.5>>
<<show_full_screen "pp2_分析3-1" 0>>
<<show_full_screen "pp2_分析3-2" 0>>
me: 这些数据就被“挤”入了另一条记忆-视觉桥。也就是被UF占据的这只眼睛里。 #line:0a6a9e6
me: 这些数据就被“挤”入了另一条记忆-视觉桥。也就是被[[term]]UF[[/term]]占据的这只眼睛里。 #line:0a6a9e6
<<wait 1>>
@@ -49,7 +49,7 @@ peipei: 可是为什么…它是今天出问题呢? #line:09a4c8e
peipei: 我是说,为什么昨天把那条线剪断,它就好了呢?<br>为什么一天之后…才变成这样呢? #line:03cb4f4
<<hide_dialog>>
<<wait 1>>
me: 在失去最后看见色彩的能力之后…你变得更想*看见*了,对吧。 #line:0f0a617
me: 在失去最后看见色彩的能力之后…你变得更想*看见*了,[[comma-pause]]<br>对吧。 #line:0f0a617
peipei: 看见? #line:004cafe
me: 看见色彩。<br>虽然你表现得似乎无所谓…但你更加无法抑制地…想要去看见色彩了。对吧? #line:06d3d87
peipei: …… #line:013c17a
@@ -64,18 +64,18 @@ peipei: …… #line:013c17a
<<show_full_screen "pp2_分析4-7" 0>>
me: 我想这就是原因。
me: 原型是一种很复杂的系统,有时越是去尝试阻断…就反而越活跃了。 #line:0722937
me: 原型是一种很复杂的系统,[[comma-pause]]<br>有时越是去尝试阻断…就反而越活跃了。 #line:0722937
<<hide_dialog>>
<<wait 1>>
me: 要真正地阻断…或者说,*根除*这种想象力…就必须根除它的起点…… #line:0e0ab54
me: 要真正地阻断…或者说,[[comma-pause]]<br>*根除*这种想象力…就必须根除它的起点…… #line:0e0ab54
me: 根除产生它的念头…根除…<br>色彩本身。 #line:013d79a
me: 抹除记忆中所有色彩。直到不再记得任何颜色…直到就像新生一样。 #line:00c003c
<<wait 1>>
me: 没见过色彩,也就不会再想象色彩了。那些记忆也就不会干扰UF也不会干扰视觉了。 #line:03c52a9
me: 没见过色彩,[[comma-pause]]<br>也就不会再想象色彩了。那些记忆也就不会干扰[[term]]UF[[/term]][[comma-pause]]<br>也不会干扰视觉了。 #line:03c52a9
<<wait 1>>
@@ -83,11 +83,11 @@ me: 颜色只是屏幕上显示的RGB值。 #line:01c705f
//【占位】:治疗方式1:看机体能不能抑制原型的想象bug
//【占位】:这个bug利用的漏洞似乎是记忆中的色彩,能借助色彩操纵记忆的流动
//【占位】:治疗方式2:只要清除记忆色彩,似乎也就没问题了
me: 就像世界本来就是黑白的,夕阳、绿叶……本来就是灰色的。就像一直如此。 #line:0560391
me: 就像世界本来就是黑白的,[[comma-pause]]<br>夕阳、绿叶……本来就是灰色的。就像一直如此。 #line:0560391
<<hide_dialog>>
<<wait 1>>
me: 这应该是…最好的方案了。 #line:068d2a0
<<hide_full_screem>>
<<hide_full_screen>>
peipei: ……<br>为什么…<br>…… #line:0372b04
<<fade_in 1>>
<<jump 结束分析>>
@@ -1,4 +1,4 @@
title: 进入术前谈话
title: 进入术前谈话
tags: start
colorID: 3
position: 251,-1150
@@ -35,18 +35,18 @@ me: 你的原型太脆弱,太敏感,太不受控制…… #line:0f69a12
<<wait 1>>
<<change_actor_state 1SweatBlankeye>>
<<wait 0.5>>
peipei: 我还以为…只要我能把想象的画面给UF它就能把想象也画出来…… #line:0d06cd0
me: 你理解错了。它不能理解你的想象力,也不允许想象力。 #line:00a8d00
me: UF要的是“没有”想象力,彻底的没有。 #line:0265c6b
me: 尽管它在你的身体里面,但对它来说,你的身体才是异类。 #line:0ceadc4
peipei: 我还以为…只要我能把想象的画面给[[term]]UF[[/term]][[comma-pause]]<br>它就能把想象也画出来…… #line:0d06cd0
me: 你理解错了。它不能理解你的想象力,[[comma-pause]]<br>也不允许想象力。 #line:00a8d00
me: [[term]]UF[[/term]]要的是“没有”想象力,彻底的没有。 #line:0265c6b
me: 尽管它在你的身体里面,[[comma-pause]]<br>但对它来说,你的身体才是异类。 #line:0ceadc4
<<wait 1>>
<<change_actor_state 2SweatSideeyeA>>
<<wait 1>>
peipei: 医生,原型…到底什么? #line:0b19a92
me: 那是一段在我们身体里的代码…我们的身体就像一间房子,原型就像住在身体里的一个影子… #line:05581cb
me: 没有原型的话,我们只是一个最普通的机器,只会执行最简单的指令。 #line:09e4971
me: 那是一段在我们身体里的代码…我们的身体就像一间房子,[[comma-pause]]<br>原型就像住在身体里的一个影子… #line:05581cb
me: 没有原型的话,我们只是一个最普通的机器,[[comma-pause]]<br>只会执行最简单的指令。 #line:09e4971
<<wait 1>>
@@ -67,7 +67,7 @@ me: 不要让它占据你,佩佩。这会害了你的。
peipei: 不…我是说,医生……<br>是不是只要我能…只要这个“原型”能…停止。 #line:0d553cf
<<change_actor_state_async D2S7-3>>
<<change_actor_state 1SweatLaugh>>
pp: 只要我能让那个“声音”不要再想象画面……UF是不是就能恢复正常? #line:085a1b8
pp: 只要我能让那个“声音”不要再想象画面……[[term]]UF[[/term]]是不是就能恢复正常? #line:085a1b8
me: …但是,这不是一个彻底的方案…… #line:07c095f
<<change_actor_state_async D2S7-4>>
<<change_actor_state 1SweatI>>
@@ -138,29 +138,29 @@ me: 好吧。 #line:0856470
me: 你是怎么把那些颜色和图像…调用出来的呢? #line:04df8f5
<<change_actor_state 2SideeyeA>>
//peipei: 调用?我不明白…… #line:0e314d4
peipei: 我就是…去*想象*它们是什么颜色的,然后,我就能真的看到那些颜色了。 #line:0344763
me: 好吧…*想象*。我不知道你具体是怎么做到的,但是就像你唤醒它一样,用同样的方式让它停下吧。 #line:07c01d3
peipei: 我就是…去*想象*它们是什么颜色的,[[comma-pause]]<br>然后,我就能真的看到那些颜色了。 #line:0344763
me: 好吧…*想象*。我不知道你具体是怎么做到的,[[comma-pause]]<br>但是就像你唤醒它一样,[[comma-pause]]<br>用同样的方式让它停下吧。 #line:07c01d3
<<hide_dialog>>
<<wait 1>>
<<change_actor_state 1SweatBlankeyeI>>
<<wait 1>>
peipei: 同样的方式…?可我感觉…越是去控制它,它反而好像越活跃了。 #line:0e54d35
peipei: 同样的方式…?可我感觉…越是去控制它,[[comma-pause]]<br>它反而好像越活跃了。 #line:0e54d35
->让佩佩保持专注 #line:06226e7
me: 佩佩,我需要你振作起来。 #line:0127ff8
me: 不要想别的事情,保持专注,控制住你的思绪。 #line:0e1cad0
me: 你是你自己的主人,无论是你的想象力还是你的UF,不要被它们干扰。 #line:0fef7de
me: 你是你自己的主人,[[comma-pause]]<br>无论是你的想象力还是你的[[term]]UF[[/term]],不要被它们干扰。 #line:0fef7de
peipei: 呼…好的,保持专注…… #line:0a2d4ba
->让佩佩紧张起来 #line:03a14e5
me: 这很重要,佩佩。你不想要被抹除想象,你也不想要被回收,对吧? #line:0e028e3
me: 这很重要,佩佩。你不想要被抹除想象,[[comma-pause]]<br>你也不想要被回收,对吧? #line:0e028e3
peipei: 我不想……我不知道。医生。我只是害怕…… #line:066feda
me: 现在只有你自己能做到这件事情,只有你自己才能救你自己。 #line:0a06700
me: 现在只有你自己能做到这件事情,[[comma-pause]]<br>只有你自己才能救你自己。 #line:0a06700
peipei: 你说得对,医生。 #line:09203ee
->让佩佩放松 #line:0faaf11
me: 别害怕,佩佩。你生病了,这不是你的错。 #line:0ce4de5
me: 我知道你很痛苦,这场治疗结束你就康复了。很快就结束了。 #line:0feaaea
me: 我需要你配合才能完成这场治疗,我希望你能保持轻松,不要太紧张。 #line:06321e6
me: 我知道你很痛苦,[[comma-pause]]<br>这场治疗结束你就康复了。很快就结束了。 #line:0feaaea
me: 我需要你配合才能完成这场治疗,[[comma-pause]]<br>我希望你能保持轻松,不要太紧张。 #line:06321e6
peipei: 好的,这不是我的错……这不是我的错。 #line:0a9ac05
peipei: 很快就结束了…… #line:098c708
me: 很好,现在闭上眼睛。 #line:041c698
@@ -172,30 +172,30 @@ me: 很好,现在闭上眼睛。 #line:041c698
peipei: 好。 #line:03343b5
->提示佩佩想象罐头 #line:0edb0ef
me: 你说过UF给你输入了很多关于罐头的图像,你可以试着回忆一下那些图像,那些应该是安全的。 #line:0589e2d
me: 你说过[[term]]UF[[/term]]给你输入了很多关于罐头的图像,[[comma-pause]]<br>你可以试着回忆一下那些图像,[[comma-pause]]<br>那些应该是安全的。 #line:0589e2d
peipei: 好的…我这就做…… #line:0f9e6d8
peipei: 罐头……罐头……还是罐头。 #line:017ee4f
peipei: 罐头在什么地方呢?是在海报上,还是有人拿着它?嘶… #line:074a886
peipei: 罐头在什么地方呢?是在海报上,[[comma-pause]]<br>还是有人拿着它?嘶… #line:074a886
me: 什么也没有,就只想像罐头。 #line:07673db
->提示佩佩想象黑白的图像 #line:05c9e80
me: 你看到的画面本来就是黑白的,那么你应该也可以想象黑白的世界吧? #line:0b9d2f9
me: 你看到的画面本来就是黑白的,[[comma-pause]]<br>那么你应该也可以想象黑白的世界吧? #line:0b9d2f9
peipei: 我可以…… #line:0545e44
peipei: 不……好像我越去想象黑白,就反而越有颜色了。 #line:05d5523
me: 放轻松,就想象你本来就看不到颜色。想象你是一个先天看不见颜色的孩子。 #line:097ddc2
me: 放轻松,[[comma-pause]]<br>就想象你本来就看不到颜色。想象你是一个先天看不见颜色的孩子。 #line:097ddc2
peipei: 我先天就看不见颜色…… #line:02e106a
peipei: 我还是…会时不时地……医生,我的脑子好乱…痛…… #line:0e0eace
->提示佩佩什么都不要想 #line:039dc2a
me: 保持什么也不要想,深呼吸。 #line:0ecdb4b
peipei: 什么也不想…… #line:0a05482
peipei: ……这意味着我该想象一片黑色吗? #line:0d3faa9
me: 不,黑色是你看到的东西,不是你想象出来的。你就盯着视野中间一个点,什么也不要做。 #line:0aa8444
peipei: 但我好像看到了我自己的鼻子,那是我想象出来的吗?我分不清…但是它又开始痛了…… #line:0eea200
me: 不,黑色是你看到的东西,[[comma-pause]]<br>不是你想象出来的。你就盯着视野中间一个点,[[comma-pause]]<br>什么也不要做。 #line:0aa8444
peipei: 但我好像看到了我自己的鼻子,[[comma-pause]]<br>那是我想象出来的吗?我分不清…但是它又开始痛了…… #line:0eea200
me: 是的,那也是你的想象。你什么也看不到。 #line:00b9521
me: 有一个方法来清空你的思绪。 #line:08387b1
me: 如果你无法控制你的大脑活动,试着说“停止”。 #line:09498de
<<change_actor_state 1SweatIB>>
peipei: 就……说出来吗? #line:0500219
me: 是的。一旦你意识到这不是你想要去想的东西,就对自己喊“停止”,然后你就会停下来。 #line:0270a9e
me: 是的。一旦你意识到这不是你想要去想的东西,[[comma-pause]]<br>就对自己喊“停止”,[[comma-pause]]<br>然后你就会停下来。 #line:0270a9e
peipei: 让我试试…… #line:0f94543
<<change_actor_state 1SweatB>>
->让佩佩保持一段时间 #line:04ed557
@@ -1,4 +1,4 @@
title: 进入停止想象
title: 进入停止想象
tags: start
position: 428,-88
---
@@ -33,18 +33,18 @@ tags: content no_save
position: 71,-93
---
ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目标…… #line:09dd772
ULM自训练进程: [[ulm]]检测到进程接管请求!正在识别手动选择的目标…… #line:09dd772
<<UF_SwitchState UFSystem UF 0.1>>
<<UF_spriteIn UFSystem>>
//ULM自训练进程: 已池化图像卷积数据!正在上传神经网络数据集……
<<if $currentEyeTarget=="sky">>
ULM自训练进程: 目标标记,天空,蓝色,正在进行第130627次图像生成训练… #line:06b56f4
ULM自训练进程: [[ulm]]目标标记,天空,蓝色,[[comma-pause]]<br>正在进行第130627次图像生成训练… #line:06b56f4
<<elseif $currentEyeTarget=="plant">>
ULM自训练进程: 目标标记,植物,绿色,正在进行第130627次图像生成训练… #line:0a2595e
ULM自训练进程: [[ulm]]目标标记,植物,绿色,[[comma-pause]]<br>正在进行第130627次图像生成训练… #line:0a2595e
<<elseif $currentEyeTarget=="poster">>
ULM自训练进程: 目标标记,人物面部,正在进行第130627次图像生成训练… #line:0e901c0
ULM自训练进程: [[ulm]]目标标记,人物面部,[[comma-pause]]<br>正在进行第130627次图像生成训练… #line:0e901c0
<<elseif $currentEyeTarget=="computer">>
ULM自训练进程: 未识别到对应记忆,无法训练!尝试其它目标。 #line:0e5b6a0
ULM自训练进程: [[ulm]]未识别到对应记忆,无法训练!尝试其它目标。 #line:0e5b6a0
<<jump UFCheckEnd>>
<<endif>>
<<set $UFCount = $UFCount + 1>>
@@ -68,7 +68,7 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
<<UF_SwitchState UFSystem UF 3>>
ULM自训练进程: 已生成所选目标的自动训练图像!<br>图像正在上传至UF共享数据集…… #line:0cf2118
<<UF_spriteOut UFSystem 2>>
ULM自训练进程: 上传成功! #line:0725695
ULM自训练进程: [[ulm]]上传成功! #line:0725695
peipei: 太好了,医生! #line:0b11920
peipei: 只要我能一直控制住想象力,问题就解决了?对吗? #line:0c0d2e6
peipei: 是可以的!对吧?是可以的!我…我就知道我是可以的! #line:0e17299
@@ -118,9 +118,9 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
<<wait 0.6>>
<<UF_SwitchState UFSystem memory 3>>
//【生成了错误的*{$currentEyeTarget}*相关图像】 #line:0387ef4
ULM自训练进程: 已生成所选目标的自动训练图像!<br>图像正在上传至UF共享数据集…… #line:0f32ebf
ULM自训练进程: [[ulm]]已生成所选目标的自动训练图像!<br>图像正在上传至[[term]]UF[[/term]]共享数据集…… #line:0f32ebf
<<UF_spriteOut UFSystem 1>>
ULM自训练进程: 上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:021de0d
ULM自训练进程: [[ulm]]上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:021de0d
<<hide_dialog>>
<<wait 0.5>>
peipei: 啧,现在它变得这么顽固了…… #line:03119ae
@@ -139,7 +139,7 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
//【生成了错误的*{$currentEyeTarget}*相关图像】 #line:0693bc3
ULM自训练进程: 已生成所选目标的自动训练图像!<br>图像正在上传至UF共享数据集…… #line:09a85f0
<<UF_spriteOut UFSystem 1>>
ULM自训练进程: 上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:0aee23e
ULM自训练进程: [[ulm]]上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:0aee23e
peipei: 停止!…<br>不合规范?到底什么是不合规范?? #line:0cd2e59
//⭐⭐⭐做一些额外的记忆流入,出现在屏幕各个地方?⭐⭐⭐
me: 好了,快停下。<br>这下已经更严重了。 #line:057e986
@@ -148,7 +148,7 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
peipei: 继续。我必须继续… #line:06c539e
peipei: 我已经没有机会了!我必须这么做。<br>我不想变成那样…医生……我不要变成那样…… #line:066d259
//peipei: 我不要让那个念头死……它不应该死在这里。 #line:027024e
peipei: 佩佩…求求你,别再出现了……睡吧,求求你……睡吧…… #line:0931b4c
peipei: 佩佩…求求你,别再出现了……睡吧,[[comma-pause]]<br>求求你……睡吧…… #line:0931b4c
me: 佩佩。 #line:0f345b6
peipei: 不,医生,我想再试试。 #line:0c977a6
<<wait 1>>
@@ -174,9 +174,9 @@ ULM自训练进程: 检测到进程接管请求!正在识别手动选择的目
peipei: 都是因为你,都是因为你!我才…… #line:0780442
me: 佩佩,快停下。 #line:022099f
<<UF_SwitchState UFSystem UF 2>>
ULM自训练进程: 已生成所选目标的自动训练图像!<br>图像正在上传至UF共享数据集…… #line:08dffd1
ULM自训练进程: [[ulm]]已生成所选目标的自动训练图像!<br>图像正在上传至[[term]]UF[[/term]]共享数据集…… #line:08dffd1
<<UF_spriteOut UFSystem 1>>
ULM自训练进程: 上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:0b4877d
ULM自训练进程: [[ulm]]上传被拒绝:图像未通过自动审查!<br>原因:存在不合规范的内容。 #line:0b4877d
peipei: 不合规范?…什么是不合规范?…到底什么是规范?到底什么是规范??? #line:0b39ae6
<<StartUFEffect_loop UFSystem>>
peipei: 你还不明白吗?这在它们看来一点都不重要!! #line:04a54cb
@@ -374,7 +374,7 @@ me: 降不下来…为什么降不下来? #line:0153613
me: …再这样下去,佩佩就要被自己烧死了。 #line:0aa35d2
<<hide_dialog>>
<<wait 0.5>>
me: 看来只能…先把UF先拆下来了。 #line:03b6dbb
me: 看来只能…先把[[key]]UF[[/key]]先拆下来了。 #line:03b6dbb
Task: 拆除UF模块 #line:0538e87
<<HideHeatMap>>
<<jump 结束停止想象>>
@@ -0,0 +1,412 @@
# 火山 Stage6 本地化完整实施计划
## 1. 目标与边界
Stage6 继续使用项目现有的两套本地化入口:
- 固定 UIUnity Localization 的现有 `UIText` String Table Collection。
- Stage6 玩法及演出内容:新增独立的 `HuoshanStage6` String Table Collection。
- Yarn 普通对白:继续沿用项目 Yarn Spinner 本地化体系,但本轮不启用 `FP_Huoshan1` 的整章对白翻译。
- `params.csv` 继续只用于 `l10n.*` 通用短语替换,不承载 Stage6 玩法文本。
本轮纳入:
- 表达小游戏目标、输出、整合率、干扰数量等固定 UI。
- 三轮语言粒子的目标句和干扰 token。
- LOG3 释放演出中的攻击词、稳定词、失稳词等动态文字。
- 相关程序接口、Yarn 命令协议和使用文档。
- 中、英、日三种语言数据;英日文首版标记为待母语审校。
本轮明确暂缓:
- 普通角色对白。
- Task 面板中的“释放表达模块的阻塞 log”和 LOG1/2/3 任务提示。
- “左键排斥、右键吸引”等 Yarn 操作教学。
- Yarn line tag 和 Yarn 本地化 CSV。
- 调试日志、Inspector Tooltip、对象名、节点名等开发文本。
因此,本轮完成后核心小游戏及释放演出可以多语言运行,但任务面板与操作教学仍会显示中文,等待之后随对白本地化统一处理。
## 2. 本地化数据结构
### 2.1 固定 UI`UIText`
继续使用现有键,不新建重复条目:
| Key | 用途 | 中文示例 | 英文草稿 | 日文草稿 |
|---|---|---|---|---|
| `huoshan_expression_objective` | 小游戏目标 | 目标:分离红色干扰,连接蓝色真心 | OBJECTIVE: ISOLATE RED INTERFERENCE. CONNECT THE BLUE TRUTH. | 目標:赤い干渉を分離し、青い本音をつなげる |
| `huoshan_expression_output` | 输出按钮 | 输出 | OUTPUT | 出力 |
| `huoshan_expression_integration` | 整合率 | 语言整合率:{0}% | LANGUAGE INTEGRATION: {0}% | 言語統合率:{0}% |
| `huoshan_expression_interference` | 干扰数量 | 干扰思绪:{0} | INTERFERENCE SIGNALS: {0} | 干渉思考:{0} |
要求:
- 保留 `{0}` 参数,程序继续通过格式化参数填入数值。
- 英文和日文目前的中文占位必须替换。
- 固定 UI 继续使用现有 `LocalizeStringEvent``StringDatabase` 绑定。
- 切换 Locale 时立即刷新这些固定 UI。
### 2.2 玩法内容:`HuoshanStage6`
新增 String Table Collection
```text
HuoshanStage6
├─ zh-Hans
├─ en
└─ ja-JP
```
键名只表达语义,不包含语言、行号或 Yarn 节点名。
#### 语言粒子数据
```text
round.log1.target
round.log1.interference_tokens
round.log2.target
round.log2.interference_tokens
round.log3.target
round.log3.interference_tokens
```
中文源数据:
| Key | 中文值 |
|---|---|
| `round.log1.target` | 我就是个笑话 |
| `round.log1.interference_tokens` | 哈哈\|嘿嘿\|呵呵 |
| `round.log2.target` | 不要离开我 |
| `round.log2.interference_tokens` | 哈哈哈\|嘿嘿嘿\|呵呵呵\|笑死了\|好好笑 |
| `round.log3.target` | 不要那样看我 |
| `round.log3.interference_tokens` | 哈哈哈哈\|笑死我了\|太好笑了\|绷不住了\|笑不活了\|真的太搞笑 |
`interference_tokens` 的表值使用 `|` 分隔。解析规则:
- 按 `|` 拆分。
- 对每项执行 Trim。
- 丢弃空项。
- 至少保留一个有效 token,否则视为数据错误。
- token 由本地化人员针对语言重新创作,不要求与中文逐字对应。
- 英文示例:`HA|HAHA|LOL|JK`
- 日文示例:`ハハ|笑|冗談|ウケる`
#### LOG3 释放演出数据
按语义建立以下键;重复演出复用同一个键:
```text
release.attack.why
release.attack.dont_believe_me
release.attack.i_know_im_last
release.attack.bad_weather
release.attack.cannot_sell
release.attack.try_to_explain
release.attack.refuse_to_believe
release.attack.a_little_trust
release.attack.had_enough
release.attack.damn
release.attack.useless
release.attack.prove_myself
release.attack.cannot_do_anything
release.attack.catch_up
release.attack.help_yingli
release.attack.no_courage
release.attack.say_it
release.attack.trash
release.attack.hopeless
release.attack.why_loop
release.settle.want_to_win_once
release.destabilize.dont_look_at_me
```
映射规则:
| 中文文本 | Key |
|---|---|
| 为什么 | `release.attack.why` |
| 不相信我 | `release.attack.dont_believe_me` |
| 我明白我垫底 | `release.attack.i_know_im_last` |
| 天气真的不行 | `release.attack.bad_weather` |
| 卖不出去 | `release.attack.cannot_sell` |
| 努力解释 | `release.attack.try_to_explain` |
| 不愿意相信我 | `release.attack.refuse_to_believe` |
| 一点信任 | `release.attack.a_little_trust` |
| 受够了 | `release.attack.had_enough` |
| 真该死 | `release.attack.damn` |
| 真没用 | `release.attack.useless` |
| 证明自己 | `release.attack.prove_myself` |
| 什么都做不到 | `release.attack.cannot_do_anything` |
| 追上你 | `release.attack.catch_up` |
| 帮到英理 | `release.attack.help_yingli` |
| 没有勇气 | `release.attack.no_courage` |
| 说出来 | `release.attack.say_it` |
| 废物 | `release.attack.trash` |
| 烂泥扶不上墙 | `release.attack.hopeless` |
| 为什么为什么 | `release.attack.why_loop` |
| 我想赢一次 | `release.settle.want_to_win_once` |
| 不要那样看我 | `release.destabilize.dont_look_at_me` |
Yarn 中重复出现的“受够了”“废物”“为什么为什么”继续保持原来的重复次数和演出参数,只复用同一条本地化键,不能因去重而改变演出节奏。
未来如果 Stage6 使用 `expression_system_message` 或其他会显示字符串的释放命令,也必须新增对应的 `release.*` 键,不能直接写玩家可见文本。
### 2.3 表格元数据
英日文第一版可以直接投入开发测试,但每个非中文条目增加统一备注:
```text
DRAFT / NEEDS NATIVE REVIEW
Context: HuoShan Stage6 expression minigame
```
释放演出条目应额外注明:
- 显示形式,例如瞬间闪现、连续攻击或最终稳定文字。
- 建议长度。
- 是否允许创译。
- 重复次数由 Yarn 控制,本地化值本身不要手工重复。
## 3. 程序接口和数据流
### 3.1 本地化引用协议
Yarn 命令参数支持两种输入:
```text
@h6.<entry-key> 从 HuoshanStage6 String Table 解析
普通字符串 按原始字符串使用,保持旧内容兼容
```
示例:
```yarn
<<start_expression
"@h6.round.log1.interference_tokens"
"@h6.round.log1.target"
"LOG1梳理完成"
16>>
<<expression_truth_attack "@h6.release.attack.why" 0.30 0.32>>
<<expression_truth_settle "@h6.release.settle.want_to_win_once" 0.55>>
<<expression_truth_destabilize
"@h6.release.destabilize.dont_look_at_me"
1.6
0.05
22
"FF304D">>
```
其中:
- 前两个 `start_expression` 参数是本地化内容。
- 完成节点 `LOG1梳理完成` 是 Yarn 流程标识,不做本地化。
- 粒子数量、持续时间、颜色、Timeline、memory preset 等均为结构参数,不做本地化。
### 3.2 建议公共数据结构
```csharp
public readonly struct Stage6TextReference
{
public string RawValue { get; }
public bool IsLocalized { get; }
public string EntryKey { get; }
}
public sealed class Stage6RoundText
{
public string TargetText { get; init; }
public IReadOnlyList<string> InterferenceTokens { get; init; }
}
public sealed class Stage6LocalizationContext
{
public const string TableName = "HuoshanStage6";
public const string ReferencePrefix = "@h6.";
public Locale RoundLocale { get; private set; }
public IEnumerator BeginRound(Locale locale);
public IEnumerator Resolve(
string textReference,
Action<string> completed);
public IEnumerator Resolve(
string textReference,
Locale locale,
Action<string> completed);
public IEnumerator LoadRound(
string roundId,
Locale locale,
Action<Stage6RoundText> completed);
}
```
职责划分:
- `Stage6TextReference`:判断参数是本地化引用还是兼容用原始字符串。
- `Stage6LocalizationContext`:统一访问 `HuoshanStage6`,避免每个 Yarn 命令各写一套解析逻辑。
- `Stage6RoundText`:向粒子系统提供已经解析完成的数据;粒子系统不直接访问 String Table。
- `LanguageYarnCommand`:在启动小游戏前等待本地化解析完成,再把纯文本数据交给 `LanguageParticleManager`
- Log 释放相关 Yarn Command:显示前通过同一 resolver 解析字符串,再交给 `LogReleasePresentationController`
- `LanguageParticleManager``LogReleasePresentationController` 最终只接收已解析的文本,不关心键、表名或 Locale。
### 3.3 解析和错误处理
解析规则必须集中实现:
1. 参数不以 `@h6.` 开头时,直接返回原字符串。
2. 参数以 `@h6.` 开头时,取后半部分作为 Entry Key。
3. Entry Key 为空时记录错误,并显示可识别的错误占位。
4. 优先获取指定 Locale 的条目。
5. 当前语言缺失时回退到 `zh-Hans` 的同名条目。
6. 中文源条目也缺失时:
- 输出明确错误日志,包含 Table、Key、Locale 和调用命令。
- 玩家画面显示 `⟦缺失的键名⟧`
- 不得直接显示 `@h6.xxx`,也不能静默返回空字符串。
7. `interference_tokens` 解析后为空时终止该次 `start_expression`,输出数据错误,避免粒子生成阶段产生难以定位的异常。
### 3.4 Locale 切换行为
固定 UI
- `objective``output``integration``interference` 监听 `SelectedLocaleChanged`
- 切换语言后立即刷新。
- 当前数值必须保留,只替换格式模板。
语言粒子:
- 每次 `start_expression` 开始时捕获当前 Locale。
- 同一轮的目标句、目标粒子和干扰 token 全部使用该快照。
- 轮次进行中切换 Locale,不重建、不替换现有粒子。
- 下一次 `start_expression` 使用新的 Locale。
- 异步解析结束前不得开始生成粒子。
LOG 释放命令:
- 每条命令执行时解析其文字参数。
- 如果该命令属于仍在进行的表达轮次,沿用该轮的 Locale 快照。
- 没有活动轮次时使用命令执行时的当前 Locale。
- 单次命令解析完成后再启动对应演出,避免先显示 key、后替换文字。
### 3.5 Unicode 与粒子拆分
目标句不能按 UTF-16 `char``string.Length` 拆分,必须使用 Unicode 文本元素/字素簇。
规则:
- 中文汉字、日文假名、拉丁字母、组合音标均按完整文本元素处理。
- 英文空格不生成粒子,但在成功连接后的排版中形成单词间距。
- 标点是否生成粒子沿用当前玩法规则,但不能被拆成代理项。
- 有效目标粒子数量、完成判断和候选粒子数量都基于同一套文本元素结果。
- 干扰 token 是本地化短词;生成粒子时再按同样的文本元素规则取字符。
- 不使用英文 `string.Length` 直接决定粒子数量。
## 4. 具体改动安排
### 4.1 Unity Localization 资源
- 补齐 `UIText` 中四个现有火山 UI 键的英日文。
- 新建 `HuoshanStage6` Collection 及中、英、日三张表。
- 将 Collection 按项目现有 Localization Addressables 方式纳入构建。
- 配置英日文字体或 TMP fallback,覆盖拉丁字符、日文假名、常用标点。
### 4.2 语言粒子系统
- `LanguageYarnCommand` 接入统一的 `@h6.` resolver。
- `start_expression` 在调用 `LanguageParticleManager` 前解析目标句和 token。
- `LanguageParticleManager` 接收 `Stage6RoundText` 或等价的纯数据参数。
- 保留现有原始字符串调用方式,避免其他测试场景和旧 Yarn 立即失效。
- 将固定 UI 的中文常量仅作为开发兜底,不作为正式语言数据来源。
### 4.3 LOG 释放演出
- 所有接受玩家可见字符串的释放命令接入相同 resolver。
- 将 Stage6 中的攻击、settle、destabilize 参数改为 `@h6.` 引用。
- 不修改数值演出参数、调用顺序和重复次数。
- `LogReleasePresentationController` 保持表现层职责,只接收最终显示文本。
### 4.4 Yarn 内容
- 仅替换本地化范围内的 Yarn 命令参数。
- 不添加或修改 `#line:` 标签。
- 不配置 `FP_Huoshan1.yarnproject` 的整章 localization。
- 不生成 Stage6 Yarn 翻译 CSV。
- 被注释的示例命令不需要建立正式本地化条目;若未来启用,必须先补键。
- Task 行、鼠标教学和普通对白保持原样。
### 4.5 Prefab、图片与文档
- `ExpressManager.prefab` 当前没有确认的运行时玩家文本,也不是 Stage6 正式文本数据源,本轮不向其中写多语言文案。
- `ExpressionScreen_NoText.png` 已确认无烘焙文字,不制作多语言 Sprite。
- 场景或 Prefab 中诸如“输出”的序列化 TMP 文本只作为编辑器预览;运行时必须由 `UIText` 覆盖。
- 更新 `LogReleaseYarnCommands.md`
- 说明 `@h6.` 引用格式。
- 列出哪些参数可本地化、哪些是结构参数。
- 提供 attack、settle、destabilize 示例。
- 记录回退与错误显示规则。
- 同步更新语言系统说明文档中的 `start_expression` 示例,展示本地化键和旧原始字符串两种写法。
## 5. 测试与验收
### 5.1 数据完整性测试
- `HuoshanStage6` 的中、英、日表拥有相同键集合。
- 中文源条目不得为空。
- 英日文不得残留中文占位;允许通过备注标记待审校。
- 所有 `interference_tokens` 至少包含一个有效 token。
- 所有 Stage6 中实际执行的本地化命令参数都能解析。
- 结构参数没有被误当成本地化键。
- 扫描 Stage6 后,剩余硬编码玩家文本必须全部属于已声明的暂缓范围。
### 5.2 编辑模式测试
- `@h6.key` 能解析指定 Locale。
- 普通字符串保持原值。
- 缺少目标语言时回退中文。
- 三种语言都缺失时显示 `⟦key⟧` 并记录错误。
- token 能正确 Trim 和过滤空项。
- Unicode 文本元素拆分能处理中文、英文、日文和组合字符。
- 英文空格不生成粒子,但结果排版保留词间距。
- 重复攻击词引用同一 Key 时仍能按 Yarn 调用次数重复播放。
### 5.3 PlayMode/场景验收
分别使用 `zh-Hans``en``ja-JP` 运行 LOG1、LOG2、LOG3
- 固定 UI 显示正确,格式参数正确。
- 每轮目标句和干扰 token 使用对应语言。
- 粒子连接、排斥、吸引和完成判断不受文本长度影响。
- LOG3 attack、settle、destabilize 全部显示对应语言。
- 演出调用顺序、节奏、颜色和持续时间与改造前一致。
- 中途切换语言时,固定 UI 立即更新,当前轮粒子不变,下一轮使用新语言。
- 字体无缺字方框。
- 英文长文本和日文文本没有溢出、异常换行或关键画面遮挡。
### 5.4 回归验收
- 使用旧的原始字符串调用 `start_expression` 仍然可以运行。
- `languageTest` 等测试内容不因新增 resolver 失效。
- Yarn 编译无错误。
- Stage6 的完成节点跳转保持不变。
- Task 完成逻辑和 Yarn line ID 不受影响。
- Addressables/Localization 构建包含新增表格。
- 在 Unity Editor 中完成最终视觉检查;当前无法通过 Unity MCP 自动进行场景级检查。
## 6. 交付判定
满足以下条件后视为本轮完成:
- Stage6 固定小游戏 UI 已具备中英日数据。
- 三轮粒子内容和全部正式 LOG 释放文本均从 `HuoshanStage6` 获取。
- Yarn 中相关硬编码文本已替换为 `@h6.` 引用。
- 原始字符串调用保持兼容。
- Locale 快照、回退、缺键和 Unicode 拆分规则均有测试覆盖。
- 文档足以让后续策划直接新增本地化键并在 Yarn 命令中使用。
- 任务提示、操作教学和普通对白仍明确列为后续 Yarn 本地化工作,不被误报为已完成。
+1 -1
View File
@@ -133,7 +133,7 @@ PlayerSettings:
vulkanEnableLateAcquireNextImage: 0
vulkanEnableCommandBufferRecycling: 1
loadStoreDebugModeEnabled: 0
bundleVersion: 0.6.0.4-20260725-214901
bundleVersion: 0.6.0.5-20260726-212018
preloadedAssets:
- {fileID: 11400000, guid: 0fb2bc2476953fc43a74f0ab8879077c, type: 2}
metroInputSource: 0
+812
View File
@@ -0,0 +1,812 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import os
import re
import sys
import tempfile
import unicodedata
from collections import Counter, defaultdict
from pathlib import Path
LINE_TAG_RE = re.compile(r"(?<!\\)#line:([0-9A-Za-z_-]+)")
META_TAG_RE = re.compile(r"(?<!\\)\s*#(?:line:[0-9A-Za-z_-]+|auto_next|autonext|option_prompt)\b")
COMMAND_RE = re.compile(r"^<<.*?>>(?:\s*//.*)?\s*$")
INLINE_IF_RE = re.compile(r"<<if\b.*?>>")
ANGLE_TAG_RE = re.compile(r"<[^>]+>")
YARN_MARKUP_RE = re.compile(r"\[\[/?[^\]]+\]\]")
CHAR_ATTR_RE = re.compile(r"^\[character\b[^\]]*/\]")
SPEAKER_RE = re.compile(r"^([^:\s][^:]{0,29})[:]\s*(.*)$")
REPEAT_RE = re.compile(r"([\u3400-\u9fff]{2,6})\1")
WEAK_LINE_START = tuple("的地得了着过而但就也却吗呢啊吧呀")
TERM_PATTERNS = (
"UF模块", "ULM自训练进程", "UF", "ULM", "UnstableFusion", "非稳态聚合模块",
"幸福罐头公司", "色觉晶圆", "原型泄露", "表达模块", "情绪模块", "逻辑模块",
"销售语言拓展模块", "记忆打孔带", "视觉模块",
)
REVIEW_ACCEPTED = "接受修改"
REVIEW_KEEP = "保留原文"
REVIEW_FOLLOW_NOTE = "按照备注修改"
MANUAL_BREAK_WIDTH = 18.0
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def visible_width(text: str) -> float:
total = 0.0
for ch in text:
if unicodedata.combining(ch):
continue
total += 1.0 if unicodedata.east_asian_width(ch) in "WFA" else 0.5
return total
def strip_markup(text: str) -> str:
text = ANGLE_TAG_RE.sub("", text)
text = YARN_MARKUP_RE.sub("", text)
return text.replace("\\#", "#").strip()
def review_choice(value) -> str:
if isinstance(value, list):
return value[0] if value else ""
return value or ""
def replace_ascii_quotes(text: str) -> str:
result = []
opening = True
for char in text:
if char != '"':
result.append(char)
continue
result.append("" if opening else "")
opening = not opening
return "".join(result)
def dialogue_parts(raw: str) -> tuple[str, str, str]:
"""Return the immutable dialogue prefix, editable body, and metadata suffix."""
metadata = META_TAG_RE.search(raw)
source = raw[:metadata.start()].rstrip() if metadata else raw.rstrip()
suffix = raw[len(source):]
cursor = 0
leading = re.match(r"^\s*(?:->\s*)?", source)
if leading:
cursor = leading.end()
attr = CHAR_ATTR_RE.match(source[cursor:])
if attr:
cursor += attr.end()
whitespace = re.match(r"\s*", source[cursor:])
cursor += whitespace.end() if whitespace else 0
speaker = SPEAKER_RE.match(source[cursor:])
if speaker and not any(c in speaker.group(1) for c in "<>[]{},。!?…"):
cursor += speaker.start(2)
return source[:cursor], source[cursor:], suffix
def wrap_phrases(text: str, phrases: list[str], marker: str) -> str:
phrases = sorted({x for x in phrases if x}, key=len, reverse=True)
if not phrases:
return text
pattern = re.compile("|".join(re.escape(x) for x in phrases))
return pattern.sub(lambda match: f"[[{marker}]]{match.group(0)}[[/{marker}]]", text)
def detected_terms(text: str) -> list[str]:
plain = strip_markup(text)
return [term for term in TERM_PATTERNS if term in plain]
def comma_break_positions(text: str) -> tuple[list[int], list[float]]:
visible_positions = []
width = 0.0
index = 0
while index < len(text):
if text[index] == "<":
close = text.find(">", index + 1)
if close >= 0:
index = close + 1
continue
if text.startswith("[[", index):
close = text.find("]]", index + 2)
if close >= 0:
index = close + 2
continue
char = text[index]
width += visible_width(char)
if char == "":
visible_positions.append((index, width))
index += 1
return [x[0] for x in visible_positions], [x[1] for x in visible_positions] + [width]
def add_comfortable_breaks(text: str) -> str:
if re.search(r"(?i)<br\s*/?>", text):
return text
positions, widths = comma_break_positions(text)
if not positions:
return text
total_width = widths[-1]
target_lines = max(1, int((total_width + MANUAL_BREAK_WIDTH - 0.001) // MANUAL_BREAK_WIDTH))
break_count = min(target_lines - 1, len(positions))
if break_count <= 0:
return text
comma_widths = widths[:-1]
selected = []
previous = -1.0
for part in range(1, break_count + 1):
target = total_width * part / (break_count + 1)
candidates = [
(abs(width - target), idx, width)
for idx, width in zip(positions, comma_widths)
if width > previous + 4 and idx not in selected
]
if not candidates:
continue
_, chosen, chosen_width = min(candidates)
selected.append(chosen)
previous = chosen_width
for index in sorted(selected, reverse=True):
insert_at = index + 1
wait = re.match(r"<waitfor\s*=\s*[^>]+>", text[insert_at:], flags=re.IGNORECASE)
if wait:
insert_at += wait.end()
insertion = "<br>"
else:
insertion = "[[comma-pause]]<br>"
text = text[:insert_at] + insertion + text[insert_at:]
return text
def apply_note(body: str, note: str, category: str) -> str:
quoted = re.findall(r"“([^”]+)”", note)
if "直接去掉#autonext" in note:
return body
if "改为“色觉晶圆”" in note:
body = re.sub(r"(?<!色觉)晶圆", "色觉晶圆", body)
return wrap_phrases(body, ["色觉晶圆"], "term")
if "改为“码”" in note:
body = body.replace("", "")
if "去掉“*”" in note:
body = body.replace("*", "")
if "加wiggle" in note:
for phrase in quoted:
emphasized = f"*{phrase}*"
if emphasized in body:
body = body.replace(
emphasized, f"*[[joke]]{phrase}[[/joke]]*")
else:
body = wrap_phrases(body, [phrase], "joke")
return body
if "" in note and ("断行" in note or "换行" in note):
return add_comfortable_breaks(body)
if "并用蓝字" in note:
body = wrap_phrases(body, detected_terms(body), "term")
elif note.startswith("用黄字"):
body = wrap_phrases(body, detected_terms(body), "key")
elif "专有名词" in category:
body = wrap_phrases(body, detected_terms(body), "term")
if quoted and ("黄字" in note):
body = wrap_phrases(body, quoted, "key")
if "断行" in note:
body = add_comfortable_breaks(body)
return body
def transform_reviewed_line(row: dict) -> str:
original = row["原文"]
decision = review_choice(row.get("审阅结论"))
if decision == REVIEW_KEEP:
return original
note = row.get("审阅备注") or ""
category = row.get("问题分类") or ""
suggested = row.get("建议文本") or ""
if decision == REVIEW_ACCEPTED and suggested:
return suggested
prefix, body, suffix = dialogue_parts(original)
if decision == REVIEW_FOLLOW_NOTE:
if "直接去掉#autonext" in note:
return original.replace("#autonext", "")
body = apply_note(body, note, category)
elif decision == REVIEW_ACCEPTED:
if "标点规范" in category:
body = replace_ascii_quotes(body)
if "空格" in category:
body = re.sub(r"\s+([,。!?;:])", r"\1", body)
if "专有名词" in category and "[[term]]" in (row.get("建议标记") or ""):
body = wrap_phrases(body, detected_terms(body), "term")
if "ULM效果" in category and "[[ulm]]" in (row.get("建议标记") or ""):
prefix += "[[ulm]]"
if "笑话效果" in category and "[[joke]]" in (row.get("建议标记") or ""):
body = f"[[joke]]{body}[[/joke]]"
if "换行候选" in category:
body = add_comfortable_breaks(body)
return prefix + body + suffix
def source_without_metadata(raw: str) -> str:
match = META_TAG_RE.search(raw)
return raw[:match.start()].rstrip() if match else raw.rstrip()
def split_speaker(text: str) -> tuple[str, str]:
attr = CHAR_ATTR_RE.match(text)
if attr:
text = text[attr.end():].lstrip()
match = SPEAKER_RE.match(text)
if not match:
return "", text
speaker = match.group(1).strip()
if any(c in speaker for c in "<>[]{},。!?…"):
return "", text
return speaker, match.group(2)
def issue(issue_type: str, severity: str, confidence: str, reason: str,
proposal: str = "", suggested_text: str = "") -> dict:
return {
"category": issue_type,
"severity": severity,
"confidence": confidence,
"reason": reason,
"proposal": proposal,
"suggested_text": suggested_text,
}
def detect(record: dict) -> list[dict]:
text = record["text"]
visible = record["visible_text"]
speaker = record["speaker"]
found: list[dict] = []
if REPEAT_RE.search(visible):
found.append(issue("重复候选", "需确认", "", "发现相邻重复片段,可能是强调或口语节奏,也可能是复制错误。"))
if re.search(r"[,。!?;:]{2,}", visible):
found.append(issue("标点", "建议优化", "", "发现连续中文标点,需确认是否为误输入。"))
if "..." in visible or re.search(r'["\']', visible):
found.append(issue("标点规范", "建议优化", "", "包含 ASCII 省略号或引号,建议统一为中文标点。"))
if re.search(r"\s+[,。!?;:]", visible):
found.append(issue("空格", "明确错误", "", "中文标点前存在多余空格。"))
if re.search(r"(慢慢|悄悄|轻轻|渐渐|狠狠|迅速|飞快|大声|小声)的(?=[\u3400-\u9fff])", visible):
found.append(issue("的地得", "建议优化", "", "状语后疑似误用“的”,建议结合谓语确认是否改为“地”。"))
segments = [strip_markup(x) for x in re.split(r"(?i)<br\s*/?>", text)]
widths = [visible_width(x) for x in segments]
if len(segments) > 1:
bad_start = any(seg.startswith(WEAK_LINE_START) for seg in segments[1:] if seg)
orphan = any(0 < width <= 2 for width in widths)
imbalance = max(widths, default=0) - min(widths, default=0) >= 8
if bad_start or orphan or imbalance:
found.append(issue("换行", "建议优化", "",
"现有断行存在弱助词开头、孤行或明显失衡;仅在不增加显示行数时调整。"))
elif visible_width(visible) > 24 and "" in visible:
found.append(issue("换行候选", "需确认", "",
"单行较长且含逗号,可在保持总显示行数的前提下评估断行。"))
if speaker in {"me", "mecd", "med"} and (
re.match(r"^[(].*[)]$", visible) or any(k in visible for k in ("小声", "低声", "喃喃", "默念"))
) and "[[aside]]" not in text and "#5F5F5F" not in text:
found.append(issue("自言自语/念文本", "需确认", "",
"疑似自言自语、阅读或低声表达,请确认灰色语义标记。", "[[aside]]…[[/aside]]"))
if speaker == "ULM自训练进程" and "[[ulm]]" not in text and "vertexp" not in text:
found.append(issue("ULM效果", "样式标记", "",
"活动 ULM 自训练进程文本应使用统一出现效果。", "[[ulm]]"))
terms = [term for term in TERM_PATTERNS if term in visible]
if terms and "[[term]]" not in text and "#52B6FF" not in text and "#FFB200" not in text:
found.append(issue("专有名词", "需确认", "",
"包含游戏专有名词:" + "".join(terms) + "。颜色需按本次语境逐处确认,不因术语出现而自动标色。",
"[[term]]…[[/term]]"))
if record["module"] == "FP_Huoshan1" and speaker == "hs" and (
any(k in visible.replace("*", "") for k in (
"看医生其实", "衣 帽", "衣帽间", "路师", "凶柿", "西红凶", "帽问题", "嫡中嫡"
))
) and "[[joke]]" not in text and "<wiggle" not in text:
found.append(issue("笑话效果", "样式标记", "",
"疑似实际讲笑话的台词;确认后使用统一 wiggle 语义标记。", "[[joke]]…[[/joke]]"))
known_replacements = {
"信号接受正常": "信号接收正常",
"2000-1000hz": "2000-10000hz",
"打折回旋": "打着回旋",
"VERYFYING": "VERIFYING",
"检查到特征频域异常": "检测到特征频域异常",
"#autonext": "#auto_next",
}
raw_source = record.get("raw", record.get("source_text", ""))
for wrong, right in known_replacements.items():
if wrong in raw_source:
found.append(issue("错别字/漏字", "明确错误", "",
f"检测到明确文本错误:{wrong}{right}",
suggested_text=raw_source.replace(wrong, right)))
# Do not duplicate categories for one source line.
merged = {}
for item in found:
merged.setdefault(item["category"], item)
return list(merged.values())
def parse_file(path: Path, repo: Path) -> tuple[list[dict], dict]:
lines = path.read_text(encoding="utf-8-sig").splitlines()
records: list[dict] = []
node = ""
pending_title = ""
in_body = False
counts = Counter()
file_hash = sha256(path)
rel = path.relative_to(repo).as_posix()
module = next((part for part in path.parts if part.startswith("FP_")), path.parent.name)
for number, raw in enumerate(lines, 1):
stripped = raw.strip()
if not in_body:
if stripped.startswith("title:"):
pending_title = stripped.split(":", 1)[1].strip()
if stripped == "---":
node = pending_title
in_body = True
counts["delimiter"] += 1
elif stripped:
counts["metadata"] += 1
else:
counts["blank"] += 1
continue
if stripped == "===":
in_body = False
counts["delimiter"] += 1
continue
if not stripped:
counts["blank"] += 1
continue
if stripped.startswith("//"):
counts["comment"] += 1
continue
if COMMAND_RE.match(stripped):
counts["command"] += 1
continue
kind = "option" if stripped.startswith("->") else "text"
active = stripped[2:].lstrip() if kind == "option" else stripped
active = INLINE_IF_RE.sub("", active).rstrip()
source_text = source_without_metadata(active)
line_tag_match = LINE_TAG_RE.search(raw)
line_tag = line_tag_match.group(1) if line_tag_match else ""
speaker, text = split_speaker(source_text)
clean = strip_markup(text)
record_id = hashlib.sha1(f"{rel}:{number}:{raw}".encode("utf-8")).hexdigest()[:12]
record = {
"id": record_id,
"module": module,
"file": rel,
"file_sha256": file_hash,
"node": node,
"line": number,
"line_tag": line_tag,
"kind": kind,
"speaker": speaker,
"raw": raw,
"source_text": source_text,
"text": text,
"visible_text": clean,
"display_lines": len(re.split(r"(?i)<br\s*/?>", text)),
}
findings = detect(record)
record["findings"] = findings
record["review_status"] = "建议修改" if any(
x["confidence"] == "" or x["severity"] == "明确错误" for x in findings
) else ("需确认" if findings else "无问题")
record["review_method"] = "确定性全量筛查;语义候选待表内确认"
records.append(record)
counts[kind] += 1
unknown = 0 if not in_body else 1
summary = {
"module": module,
"file": rel,
"sha256": file_hash,
"physical_lines": len(lines),
"active_records": len(records),
"reviewed_records": len(records),
"unreviewed_records": 0,
"issue_records": sum(bool(r["findings"]) for r in records),
"unknown_active_lines": unknown,
"counts": dict(counts),
"status": "通过" if unknown == 0 else "失败",
}
return records, summary
def write_csv(path: Path, fields: list[str], rows: list[dict]) -> None:
with path.open("w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
writer.writeheader()
writer.writerows(rows)
def scan(args: argparse.Namespace) -> int:
repo = Path(args.repo).resolve()
output = Path(args.output)
if not output.is_absolute():
output = (repo / output).resolve()
output.mkdir(parents=True, exist_ok=True)
files = []
for root_arg in args.root:
root = (repo / root_arg).resolve() if not Path(root_arg).is_absolute() else Path(root_arg)
files.extend(sorted(root.rglob("*.yarn")))
files = sorted(set(files))
all_records, coverage = [], []
for file in files:
records, summary = parse_file(file, repo)
all_records.extend(records)
coverage.append(summary)
reviewed_ids = set()
if args.review_memory:
memory = json.loads(Path(args.review_memory).read_text(encoding="utf-8"))
for row in memory:
expected = transform_reviewed_line(row)
fingerprint = f'{row["文件"]}:{int(row["行号"])}:{expected}'
reviewed_ids.add(hashlib.sha1(fingerprint.encode("utf-8")).hexdigest()[:12])
for record in all_records:
if record["id"] in reviewed_ids:
record["findings"] = []
record["review_status"] = "无问题"
record["review_method"] = "人工审阅结论已应用"
issues_by_file = Counter(
record["file"] for record in all_records if record["findings"])
for summary in coverage:
summary["issue_records"] = issues_by_file[summary["file"]]
issues = []
for record in all_records:
if not record["findings"]:
continue
categories = [x["category"] for x in record["findings"]]
severity_order = {"明确错误": 3, "样式标记": 2, "建议优化": 1, "需确认": 0}
confidence_order = {"": 2, "": 1, "": 0}
severity = max((x["severity"] for x in record["findings"]), key=lambda x: severity_order.get(x, 0))
confidence = max((x["confidence"] for x in record["findings"]), key=lambda x: confidence_order.get(x, 0))
issues.append({
"稳定ID": record["id"], "模块": record["module"], "文件": record["file"],
"节点": record["node"], "行号": record["line"], "LineTag": record["line_tag"],
"说话者": record["speaker"], "问题分类": categories, "严重度": severity,
"置信度": confidence, "原文": record["raw"],
"建议文本": next((x["suggested_text"] for x in record["findings"] if x["suggested_text"]), ""),
"理由": "\n".join(x["reason"] for x in record["findings"]),
"建议标记": "".join(x["proposal"] for x in record["findings"] if x["proposal"]),
"检测来源": "脚本规则", "审核状态": "待确认",
"file_sha256": record["file_sha256"], "display_lines": record["display_lines"],
})
manifest = {
"version": 1,
"repo": str(repo),
"roots": args.root,
"files": coverage,
}
total = {
"files": len(files),
"physical_lines": sum(x["physical_lines"] for x in coverage),
"active_records": len(all_records),
"reviewed_records": len(all_records),
"unreviewed_records": 0,
"issue_records": len(issues),
"unknown_active_lines": sum(x["unknown_active_lines"] for x in coverage),
"review_method": "确定性全量筛查;语义候选待表内确认",
}
(output / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
(output / "summary.json").write_text(json.dumps(total, ensure_ascii=False, indent=2), encoding="utf-8")
with (output / "ledger.jsonl").open("w", encoding="utf-8") as f:
for row in all_records:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
(output / "issues.json").write_text(json.dumps(issues, ensure_ascii=False, indent=2), encoding="utf-8")
issue_fields = ["稳定ID","模块","文件","节点","行号","LineTag","说话者","问题分类","严重度",
"置信度","原文","建议文本","理由","建议标记","检测来源","审核状态"]
csv_issues = [{**x, "问题分类": "".join(x["问题分类"])} for x in issues]
write_csv(output / "issues.csv", issue_fields, csv_issues)
coverage_fields = ["module","file","sha256","physical_lines","active_records","reviewed_records",
"unreviewed_records","issue_records","unknown_active_lines","status"]
write_csv(output / "coverage.csv", coverage_fields, coverage)
print(json.dumps(total, ensure_ascii=False, indent=2))
return 0 if total["unknown_active_lines"] == total["unreviewed_records"] == 0 else 2
def verify(args: argparse.Namespace) -> int:
output = Path(args.output)
summary = json.loads((output / "summary.json").read_text(encoding="utf-8"))
ledger_count = sum(1 for line in (output / "ledger.jsonl").read_text(encoding="utf-8").splitlines() if line)
issues = json.loads((output / "issues.json").read_text(encoding="utf-8"))
ok = (
summary["unknown_active_lines"] == 0
and summary["unreviewed_records"] == 0
and summary["active_records"] == ledger_count
and summary["issue_records"] == len(issues)
)
print(json.dumps({"ok": ok, "ledger_count": ledger_count, **summary}, ensure_ascii=False, indent=2))
return 0 if ok else 2
def build_lark_payloads(args: argparse.Namespace) -> int:
output = Path(args.output)
target = output / "lark"
target.mkdir(parents=True, exist_ok=True)
issues = json.loads((output / "issues.json").read_text(encoding="utf-8"))
manifest = json.loads((output / "manifest.json").read_text(encoding="utf-8"))
issue_fields = ["稳定ID","模块","文件","节点","行号","LineTag","说话者","问题分类",
"严重度","置信度","原文","建议文本","理由","建议标记","检测来源","审核状态"]
issue_rows = []
for row in issues:
issue_rows.append([
row["稳定ID"], row["模块"], row["文件"], row["节点"], row["行号"],
row["LineTag"], row["说话者"], "".join(row["问题分类"]), row["严重度"],
row["置信度"], row["原文"], row["建议文本"], row["理由"], row["建议标记"],
row["检测来源"], row["审核状态"],
])
for index in range(0, len(issue_rows), 200):
payload = {"fields": issue_fields, "rows": issue_rows[index:index + 200]}
(target / f"issues-{index // 200 + 1:03d}.json").write_text(
json.dumps(payload, ensure_ascii=False), encoding="utf-8")
coverage_fields = ["文件","模块","SHA256","物理行数","活动文本数","已审数",
"未审数","问题数","未知活动行","校验状态"]
coverage_rows = [[
row["file"], row["module"], row["sha256"], row["physical_lines"],
row["active_records"], row["reviewed_records"], row["unreviewed_records"],
row["issue_records"], row["unknown_active_lines"], row["status"],
] for row in manifest["files"]]
(target / "coverage.json").write_text(json.dumps(
{"fields": coverage_fields, "rows": coverage_rows}, ensure_ascii=False), encoding="utf-8")
style_fields = ["名称","类别","语义标记","默认参数","适用规则","示例"]
style_rows = [
["自言自语/念文本/小声","语义样式","[[aside]]…[[/aside]]","#5F5F5F","只用于真实自言自语、阅读或低声表达;斜体不自动等同","[[aside]]我是不是忘了什么……[[/aside]]"],
["游戏专有名词","语义样式","[[term]]…[[/term]]","#52B6FF","游戏世界独有的模块、机构、技术和概念","[[term]]UF模块[[/term]]"],
["玩家重点信息","语义样式","[[key]]…[[/key]]","#FFB200","任务目标、必须寻找或操作的对象;优先于专名","寻找[[key]]色觉晶圆[[/key]]"],
["逗号停顿","节奏效果","[[comma-pause]]","waitfor=0.2","只用于调整断行或明确需要节奏的逗号","别因为外面很冷,[[comma-pause]]<br>就逃回那个着火的房子。"],
["ULM自训练进程","出现效果","[[ulm]]","vertexp d=0.05 bot","FP_Peipei2 每条活动 ULM自训练进程文本","[[ulm]]ULM自训练进程: 正在训练……"],
["火山笑话","动态效果","[[joke]]…[[/joke]]","wiggle a=0.3 s=0.1","只包裹实际讲出的笑话,不包裹说明性对白","[[joke]]看医生其实是被医生看。[[/joke]]"],
]
(target / "styles.json").write_text(json.dumps(
{"fields": style_fields, "rows": style_rows}, ensure_ascii=False), encoding="utf-8")
print(json.dumps({
"issue_batches": (len(issue_rows) + 199) // 200,
"issue_rows": len(issue_rows),
"coverage_rows": len(coverage_rows),
"style_rows": len(style_rows),
}, ensure_ascii=False, indent=2))
return 0
def apply_approved(args: argparse.Namespace) -> int:
issues_path = Path(args.issues)
manifest = json.loads(Path(args.manifest).read_text(encoding="utf-8"))
repo = Path(manifest["repo"])
issues = json.loads(issues_path.read_text(encoding="utf-8"))
approved = [x for x in issues if x.get("审核状态") == "接受" and x.get("建议文本")]
hashes = {x["file"]: x["sha256"] for x in manifest["files"]}
grouped = defaultdict(list)
for row in approved:
grouped[row["文件"]].append(row)
prepared = {}
for rel, rows in grouped.items():
path = repo / rel
if sha256(path) != hashes.get(rel):
raise SystemExit(f"stale file hash: {rel}")
lines = path.read_text(encoding="utf-8-sig").splitlines(keepends=True)
for row in sorted(rows, key=lambda x: int(x["行号"])):
idx = int(row["行号"]) - 1
old = lines[idx].rstrip("\r\n")
if old != row["原文"]:
raise SystemExit(f"source mismatch: {rel}:{idx+1}")
new = row["建议文本"]
if LINE_TAG_RE.findall(old) != LINE_TAG_RE.findall(new):
raise SystemExit(f"line tag changed: {rel}:{idx+1}")
old_text = source_without_metadata(old)
new_text = source_without_metadata(new)
if len(re.split(r"(?i)<br\s*/?>", new_text)) > len(re.split(r"(?i)<br\s*/?>", old_text)):
raise SystemExit(f"display line added: {rel}:{idx+1}")
ending = lines[idx][len(lines[idx].rstrip("\r\n")):]
lines[idx] = new + ending
prepared[path] = "".join(lines)
for path, content in prepared.items():
fd, tmp = tempfile.mkstemp(prefix=path.name, dir=path.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="") as f:
f.write(content)
os.replace(tmp, path)
finally:
if os.path.exists(tmp):
os.unlink(tmp)
print(json.dumps({"applied": len(approved), "files": len(prepared)}, ensure_ascii=False))
return 0
def apply_reviewed(args: argparse.Namespace) -> int:
manifest = json.loads(Path(args.manifest).read_text(encoding="utf-8"))
reviews = json.loads(Path(args.reviews).read_text(encoding="utf-8"))
repo = Path(manifest["repo"])
hashes = {x["file"]: x["sha256"] for x in manifest["files"]}
allowed = {REVIEW_ACCEPTED, REVIEW_KEEP, REVIEW_FOLLOW_NOTE}
decisions = Counter(review_choice(row.get("审阅结论")) for row in reviews)
invalid = sorted(choice for choice in decisions if choice not in allowed)
if invalid:
raise SystemExit(f"unresolved review decisions: {invalid}")
grouped = defaultdict(list)
changes = []
unchanged_actionable = []
for row in reviews:
decision = review_choice(row.get("审阅结论"))
new = transform_reviewed_line(row)
if decision != REVIEW_KEEP and new == row["原文"]:
unchanged_actionable.append(f'{row["文件"]}:{row["行号"]} ({decision})')
if new != row["原文"]:
grouped[row["文件"]].append((row, new))
changes.append({
"稳定ID": row["稳定ID"],
"文件": row["文件"],
"行号": row["行号"],
"问题分类": row["问题分类"],
"审阅结论": decision,
"审阅备注": row.get("审阅备注") or "",
"原文": row["原文"],
"应用文本": new,
})
if unchanged_actionable:
raise SystemExit("actionable reviews produced no change:\n" + "\n".join(unchanged_actionable))
prepared = {}
for rel, rows in grouped.items():
path = repo / rel
if sha256(path) != hashes.get(rel):
raise SystemExit(f"stale file hash: {rel}")
lines = path.read_text(encoding="utf-8-sig").splitlines(keepends=True)
before_tags = LINE_TAG_RE.findall("".join(lines))
for row, new in sorted(rows, key=lambda item: int(item[0]["行号"])):
idx = int(row["行号"]) - 1
old = lines[idx].rstrip("\r\n")
if old != row["原文"]:
raise SystemExit(f"source mismatch: {rel}:{idx + 1}")
if LINE_TAG_RE.findall(old) != LINE_TAG_RE.findall(new):
raise SystemExit(f"line tag changed: {rel}:{idx + 1}")
_, old_body, _ = dialogue_parts(old)
_, new_body, _ = dialogue_parts(new)
old_width = visible_width(strip_markup(old_body))
allowed_lines = max(
len(re.split(r"(?i)<br\s*/?>", old_body)),
int((old_width + MANUAL_BREAK_WIDTH - 0.001) // MANUAL_BREAK_WIDTH),
)
new_lines = len(re.split(r"(?i)<br\s*/?>", new_body))
if new_lines > allowed_lines:
raise SystemExit(
f"display line estimate increased: {rel}:{idx + 1} "
f"({new_lines} > {allowed_lines})"
)
for marker in ("aside", "term", "key", "joke"):
if new.count(f"[[{marker}]]") != new.count(f"[[/{marker}]]"):
raise SystemExit(f"unbalanced semantic marker {marker}: {rel}:{idx + 1}")
ending = lines[idx][len(lines[idx].rstrip("\r\n")):]
lines[idx] = new + ending
content = "".join(lines)
if LINE_TAG_RE.findall(content) != before_tags:
raise SystemExit(f"file line tag set/order changed: {rel}")
prepared[path] = content
report = {
"review_records": len(reviews),
"decisions": dict(decisions),
"changed_records": len(changes),
"changed_files": len(prepared),
"dry_run": not args.apply,
"changes": changes,
}
if args.report:
Path(args.report).write_text(
json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
if args.apply:
for path, content in prepared.items():
fd, tmp = tempfile.mkstemp(prefix=path.name, dir=path.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="") as f:
f.write(content)
os.replace(tmp, path)
finally:
if os.path.exists(tmp):
os.unlink(tmp)
print(json.dumps({
key: value for key, value in report.items() if key != "changes"
}, ensure_ascii=False, indent=2))
return 0
def self_test() -> int:
import unittest
class AuditTests(unittest.TestCase):
def test_markup_and_width(self):
self.assertEqual(strip_markup("[[vertexp d=0.05 bot]]<color=\\#fff>文字</color>"), "文字")
self.assertEqual(visible_width("中文ab"), 3.0)
def test_break_rule(self):
rec = {
"text": "很多时候,比起幸福更难以让人离<br>开的,是熟悉。",
"visible_text": "很多时候,比起幸福更难以让人离开的,是熟悉。",
"speaker": "me", "source_text": "", "module": "FP_Test",
}
self.assertIn("换行", [x["category"] for x in detect(rec)])
def test_annotated_rules(self):
ulm = {"text":"正在训练","visible_text":"正在训练","speaker":"ULM自训练进程",
"source_text":"","module":"FP_Peipei2"}
self.assertIn("ULM效果", [x["category"] for x in detect(ulm)])
task = {"text":"寻找模块","visible_text":"寻找模块","speaker":"Task",
"source_text":"","module":"FP_Test"}
self.assertNotIn("重要信息", [x["category"] for x in detect(task)])
def test_reviewed_break_preserves_existing_wait(self):
text = "很多时候,比起幸福更难以让人离开的,是熟悉。"
changed = add_comfortable_breaks(text)
self.assertIn("[[comma-pause]]<br>", changed)
with_wait = "别因为外面很冷,<waitfor=0.1>就逃回那个着火的房子。"
changed_wait = add_comfortable_breaks(with_wait)
self.assertIn("<waitfor=0.1><br>", changed_wait)
self.assertNotIn("[[comma-pause]]", changed_wait)
def test_review_note_overrides_default_style(self):
row = {
"原文": "me: 去检查*情绪模块*。 #line:abc",
"审阅结论": [REVIEW_FOLLOW_NOTE],
"审阅备注": "用黄字,并且去掉“*”",
"问题分类": "专有名词",
}
changed = transform_reviewed_line(row)
self.assertIn("[[key]]情绪模块[[/key]]", changed)
self.assertNotIn("*", changed)
self.assertEqual(LINE_TAG_RE.findall(changed), ["abc"])
suite = unittest.defaultTestLoader.loadTestsFromTestCase(AuditTests)
result = unittest.TextTestRunner(verbosity=2).run(suite)
return 0 if result.wasSuccessful() else 2
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Deterministic Yarn text coverage and review ledger")
sub = parser.add_subparsers(dest="command", required=True)
p_scan = sub.add_parser("scan")
p_scan.add_argument("--repo", default=".")
p_scan.add_argument("--root", action="append", required=True)
p_scan.add_argument("--output", required=True)
p_scan.add_argument(
"--review-memory",
help="可选的飞书审阅导出;已应用或明确保留的记录不再重复报告")
p_scan.set_defaults(func=scan)
p_verify = sub.add_parser("verify")
p_verify.add_argument("--output", required=True)
p_verify.set_defaults(func=verify)
p_apply = sub.add_parser("apply")
p_apply.add_argument("--issues", required=True)
p_apply.add_argument("--manifest", required=True)
p_apply.set_defaults(func=apply_approved)
p_apply_review = sub.add_parser("apply-review")
p_apply_review.add_argument("--reviews", required=True)
p_apply_review.add_argument("--manifest", required=True)
p_apply_review.add_argument("--report")
p_apply_review.add_argument("--apply", action="store_true")
p_apply_review.set_defaults(func=apply_reviewed)
p_lark = sub.add_parser("lark-payloads")
p_lark.add_argument("--output", required=True)
p_lark.set_defaults(func=build_lark_payloads)
p_test = sub.add_parser("self-test")
p_test.set_defaults(func=lambda _: self_test())
return parser
def main() -> int:
args = build_parser().parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())