Skip to content

Commit 9238745

Browse files
committed
feat(world): 首潜创世时刻与世界数据回路闭合(宪法P1第五批)
- 世界数据回路:深潜完成→plantCoral(此前无调用方,珊瑚/地层/活力数据自此真实生长),动态import防循环依赖 - useFirstDiveStore:首潜全部完成触发创世版签名时刻(宪法第三条§5) - SignatureMoment 增加 genesis 变体:「第一颗星,亮了——从这次首潜开始,这个世界开始记住你」 - WorldConditions + RitualComplete 接线:仪式收尾展示「今日海况」一句话世界叙事(宪法第六条,仪式成为世界的每日开场),焦虑防线措辞,留存关闭时隐去
1 parent 77cfaf3 commit 9238745

6 files changed

Lines changed: 92 additions & 13 deletions

File tree

‎client/src/features/dashboard/components/ritual/RitualComplete.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Flame, Target, Sparkles } from 'lucide-react';
1616
import type { MicroGoal } from '../../types';
1717
import type { MasteryMark } from '@/types/ritual';
1818
import { useReducedMotion } from '@/hooks/useReducedMotion';
19+
import { WorldConditions } from './WorldConditions';
1920

2021
interface Props {
2122
goal?: MicroGoal;
@@ -73,6 +74,8 @@ export function RitualComplete({ goal, masteryMark, streakDays, onEnter }: Props
7374
上次内容掌握度:{MASTERY_LABEL[masteryMark]}
7475
</p>
7576
)}
77+
{/* 宪法第六条:仪式成为世界的每日开场——今日海况一句话叙事 */}
78+
<WorldConditions />
7679
</div>
7780

7881
<button
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* WorldConditions — 今日海况(宪法第六条 StartupRitual 接线)
3+
* WorldConditions — today's sea state line (constitution §6 ritual wiring)
4+
*
5+
* @ai-context: 仪式收尾的一句话世界叙事:从世界信号派生"今日海况",
6+
* 让启动仪式成为世界的每日开场。措辞遵循焦虑防线:朦胧=等待唤醒,
7+
* 零负向语言;留存关闭时不渲染(可关闭性)。
8+
*
9+
* @ai-context: One narrative line derived from world signals, shown at ritual
10+
* completion. Positive phrasing only; hidden when retention is disabled.
11+
*/
12+
import { useWorldSignalsSelect } from '@/features/retention/hooks/useWorldSignals';
13+
import { WORLD_ANCHORS } from '@/features/retention/lib/worldState';
14+
15+
export function WorldConditions() {
16+
const { mist, warmth, depthNorm, enabled } = useWorldSignalsSelect((s) => ({
17+
mist: s.mist, warmth: s.warmth, depthNorm: s.depthNorm, enabled: s.enabled,
18+
}));
19+
20+
if (!enabled) return null;
21+
22+
// 洋流温度(连击)→ 海况基调;雾(朦胧)→ 待唤醒提示;累计深度 → 潜航里程
23+
const tide = warmth >= 0.6 ? '洋流温暖' : warmth >= 0.3 ? '洋流平稳' : '洋流清凉';
24+
const visibility = mist >= 0.25 ? ',部分海域朦胧,等待唤醒' : ',能见度良好';
25+
const depth = depthNorm > 0
26+
? ` · 累计潜航 ${Math.round(depthNorm * WORLD_ANCHORS.DEPTH_FULL)} 米`
27+
: '';
28+
29+
return (
30+
<p className="text-xs text-text-tertiary text-left tracking-wide">
31+
今日海况:{tide}{visibility}{depth}
32+
</p>
33+
);
34+
}

‎client/src/features/onboarding/firstDive/useFirstDiveStore.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { DiveStepId, FirstDiveStage, FirstDiveStateV2, OnboardingProfile }
1414
import { DIVE_STEPS, orderStepsByProfile } from './diveSteps';
1515
import { loadFirstDiveState, saveFirstDiveState } from './firstDiveStorage';
1616
import { seedHandbookDeck } from './seedHandbook';
17+
import { useWorldEvents } from '@/features/retention/store/useWorldEvents';
1718

1819
/** 各首潜步骤对应的数据表计数(基线差值检测的数据源) */
1920
export type StepCounts = Record<DiveStepId, number>;
@@ -151,6 +152,11 @@ export const useFirstDiveStore = create<FirstDiveStoreState>((set, get) => ({
151152
};
152153
persist(next);
153154
set({ ...next, justCompleted: current });
155+
156+
// 宪法第三条 §5 创世版签名时刻:首潜全部完成=世界首次点亮
157+
if (allDone) {
158+
useWorldEvents.getState().emitSignatureMoment('首潜', 'genesis');
159+
}
154160
}
155161
},
156162

‎client/src/features/pomodoro/store/usePomodoroStore.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,13 @@ export const usePomodoroStore = create<PomodoroState>((set, get) => {
448448
}).catch(() => {});
449449
}).catch(() => {});
450450

451+
// 宪法第六条世界数据回路:深潜完成=种下珊瑚(沉积地层+1,世界生长)。
452+
// 动态 import 避免模块循环;plantCoral 内部受留存开关控制,失败静默降级
453+
import('@/features/retention/store/useEcosystemStore').then(({ useEcosystemStore }) => {
454+
const minutes = Math.max(1, Math.round((actualDuration ?? plannedSeconds) / 60));
455+
useEcosystemStore.getState().plantCoral(minutes, 'pomodoro', `dive-${Date.now()}`).catch(() => {});
456+
}).catch(() => {});
457+
451458
// @ai-context: 发射 session:end 事件——驱动 AI 学伴主动触发(专注结束关怀)
452459
assistantEventBus.emit('session:end', {
453460
currentHour: new Date().getHours(),

‎client/src/features/retention/components/SignatureMoment.tsx‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ type Act = 'silence' | 'event' | 'afterglow';
2929
export function SignatureMoment() {
3030
const signatureSeq = useWorldEvents((s) => s.signatureSeq);
3131
const signatureConcept = useWorldEvents((s) => s.signatureConcept);
32+
const signatureVariant = useWorldEvents((s) => s.signatureVariant);
3233
const [active, setActive] = useState(false);
3334
const [act, setAct] = useState<Act>('silence');
3435
const [concept, setConcept] = useState('');
36+
const [variant, setVariant] = useState<'mastery' | 'genesis'>('mastery');
3537
const timers = useRef<Array<ReturnType<typeof setTimeout>>>([]);
3638
const reduced = useMemo(
3739
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
@@ -54,6 +56,7 @@ export function SignatureMoment() {
5456
if (signatureSeq === 0) return;
5557
clearTimers();
5658
setConcept(signatureConcept);
59+
setVariant(signatureVariant);
5760
setActive(true);
5861

5962
if (reduced) {
@@ -143,15 +146,31 @@ export function SignatureMoment() {
143146
</div>
144147

145148
<div>
146-
<div style={{
147-
fontFamily: "'LXGW WenKai Lite','Noto Serif SC',serif",
148-
fontSize: 30, fontWeight: 700, letterSpacing: 2, color: '#E0E6F0',
149-
}}>
150-
「{concept || '这个概念'}」
151-
</div>
152-
<div style={{ fontSize: 14, color: '#90A0B8', marginTop: 12, letterSpacing: 3 }}>
153-
已成为你秩序的一部分
154-
</div>
149+
{variant === 'genesis' ? (
150+
<>
151+
<div style={{
152+
fontFamily: "'LXGW WenKai Lite','Noto Serif SC',serif",
153+
fontSize: 34, fontWeight: 700, letterSpacing: 4, color: '#E0E6F0',
154+
}}>
155+
第一颗星,亮了
156+
</div>
157+
<div style={{ fontSize: 14, color: '#90A0B8', marginTop: 14, letterSpacing: 3 }}>
158+
从这次首潜开始,这个世界开始记住你
159+
</div>
160+
</>
161+
) : (
162+
<>
163+
<div style={{
164+
fontFamily: "'LXGW WenKai Lite','Noto Serif SC',serif",
165+
fontSize: 30, fontWeight: 700, letterSpacing: 2, color: '#E0E6F0',
166+
}}>
167+
「{concept || '这个概念'}」
168+
</div>
169+
<div style={{ fontSize: 14, color: '#90A0B8', marginTop: 12, letterSpacing: 3 }}>
170+
已成为你秩序的一部分
171+
</div>
172+
</>
173+
)}
155174
</div>
156175

157176
{act === 'afterglow' && !reduced && (

‎client/src/features/retention/store/useWorldEvents.ts‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import { create } from 'zustand';
1616
/** 波纹起源锚点:模块实体 ID(场景内定位)或 'center' 场景中心 / Ripple origin */
1717
export type RippleOrigin = 'center' | 'pomodoro' | 'notes' | 'flashcards' | 'feynman' | 'inspiration';
1818

19+
/** 签名时刻变体:掌握(常规)/ 创世(首潜加长版,宪法第三条 §5) / Signature variants */
20+
export type SignatureVariant = 'mastery' | 'genesis';
21+
1922
interface WorldEventsState {
2023
/** 波纹序列号:每次 emit 自增,场景层据此生成新波纹 / Ripple sequence counter */
2124
rippleSeq: number;
@@ -26,24 +29,31 @@ interface WorldEventsState {
2629
signatureSeq: number;
2730
/** 被掌握的概念名(签名时刻文案) / Mastered concept name */
2831
signatureConcept: string;
32+
/** 签名时刻变体 / Signature variant (mastery | genesis) */
33+
signatureVariant: SignatureVariant;
2934

3035
/** 发射秩序波纹(复习/掌握的即时正反馈) / Emit an order ripple */
3136
emitOrderRipple: (origin?: RippleOrigin) => void;
32-
/** 触发签名时刻(概念掌握:费曼评估通过/卡片牢固) / Trigger signature moment */
33-
emitSignatureMoment: (concept: string) => void;
37+
/** 触发签名时刻(概念掌握/首潜创世) / Trigger signature moment */
38+
emitSignatureMoment: (concept: string, variant?: SignatureVariant) => void;
3439
}
3540

3641
export const useWorldEvents = create<WorldEventsState>((set) => ({
3742
rippleSeq: 0,
3843
rippleOrigin: 'center',
3944
signatureSeq: 0,
4045
signatureConcept: '',
46+
signatureVariant: 'mastery',
4147

4248
emitOrderRipple: (origin = 'center') => {
4349
set((s) => ({ rippleSeq: s.rippleSeq + 1, rippleOrigin: origin }));
4450
},
4551

46-
emitSignatureMoment: (concept) => {
47-
set((s) => ({ signatureSeq: s.signatureSeq + 1, signatureConcept: concept }));
52+
emitSignatureMoment: (concept, variant = 'mastery') => {
53+
set((s) => ({
54+
signatureSeq: s.signatureSeq + 1,
55+
signatureConcept: concept,
56+
signatureVariant: variant,
57+
}));
4858
},
4959
}));

0 commit comments

Comments
 (0)