feat: admit signed dynamic world surface module

This commit is contained in:
冰朔 2026-08-19 04:35:49 +08:00
commit 2b1f5d47fd
18 changed files with 584 additions and 14 deletions

View file

@ -70,6 +70,20 @@ interface BundledModuleDescriptor {
installedState: string
signatureVerified: boolean
}
interface WorldClimateSnapshot {
schema: 'hololake.world-climate/v1'
state: 'VERIFIED_LIVE' | 'TIME_ONLY_WEATHER_UNAVAILABLE'
activeDomain: string
timePhase: 'DAWN' | 'DAY' | 'DUSK' | 'NIGHT'
weatherKind: 'CLEAR' | 'CLOUD' | 'FOG' | 'RAIN' | 'SNOW' | 'STORM' | 'UNAVAILABLE'
motionIntensity: 'CALM' | 'GENTLE' | 'ACTIVE'
source: string
sourceAttributionUrl: string
cityExposed: false
coordinatesExposed: false
routingOrPermissionChanged: false
observedAtUnixMs: number
}
interface HomeStatus {
directLocalBrokerState: string
@ -408,6 +422,7 @@ function LakeAtmosphere({ awake }: { awake: boolean }) {
{starPoints.map(([left, top, size, duration], index) => <i className="world-star" key={index} style={{ left: `${left}%`, top: `${top}%`, width: size, height: size, '--star-duration': `${duration}s`, '--star-delay': `${index * .37}s` } as React.CSSProperties}/>) }
</div>
<div className="world-mist" aria-hidden="true"/>
<div className="world-climate-veil" aria-hidden="true"/>
{awake && <div className="world-shimmer" aria-hidden="true">
<i className="world-shine cool"/><i className="world-shine warm"/>
{glintPoints.map((point, index) => <i className={`world-glint ${point.warm ? 'warm' : 'cool'}`} key={index} style={{ left: `${point.left}%`, top: `${point.top}%`, width: point.size, height: point.size, '--glint-duration': `${point.duration}s`, '--glint-delay': `${point.delay}s` } as React.CSSProperties}/>) }
@ -619,6 +634,10 @@ function HoloLakeApp() {
const [mobileSyncModule, setMobileSyncModule] = useState<BundledModuleDescriptor | null>(null)
const [mobileSyncBusy, setMobileSyncBusy] = useState(false)
const [mobileSyncMessage, setMobileSyncMessage] = useState('')
const [dynamicSurfaceModule, setDynamicSurfaceModule] = useState<BundledModuleDescriptor | null>(null)
const [worldClimate, setWorldClimate] = useState<WorldClimateSnapshot | null>(null)
const [dynamicSurfaceBusy, setDynamicSurfaceBusy] = useState(false)
const [dynamicSurfaceMessage, setDynamicSurfaceMessage] = useState('')
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState('')
@ -1232,6 +1251,40 @@ function HoloLakeApp() {
finally { setMobileSyncBusy(false) }
}
const refreshDynamicSurfaceModule = async () => {
try {
const catalog = await invoke<BundledModuleDescriptor[]>('get_bundled_module_catalog')
const module = catalog.find((item) => item.moduleNumber === 'HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001') || null
setDynamicSurfaceModule(module)
if (module?.installedState === 'ACTIVE') {
const climate = await invoke<WorldClimateSnapshot>('get_world_climate')
setWorldClimate(climate)
setDynamicSurfaceMessage(climate.state === 'VERIFIED_LIVE' ? '北京时间与公开天气已映射到湖面。' : '天气源暂不可用;湖面只使用北京时间,不生成假天气。')
} else {
setWorldClimate(null)
}
return module
} catch (error) {
setWorldClimate(null)
setDynamicSurfaceMessage(humanError(error, 'system'))
return null
}
}
const activateDynamicSurfaceModule = async () => {
setDynamicSurfaceBusy(true)
setDynamicSurfaceMessage('正在验证官方签名、登记视觉编号并确认现实时间与公开天气权限……')
try {
await invoke('activate_bundled_module', { input: { moduleNumber: 'HLP-MOD-OFFICIAL-DYNAMIC-WORLD-SURFACE-0001', humanConfirmedPermissionExpansion: true } })
const module = await refreshDynamicSurfaceModule()
if (!module || module.installedState !== 'ACTIVE') throw new Error('HOLOLAKE_MODULE_NOT_ACTIVE')
} catch (error) { setDynamicSurfaceMessage(humanError(error, 'system')) }
finally { setDynamicSurfaceBusy(false) }
}
useEffect(() => {
if (repoLogin) void refreshDynamicSurfaceModule()
else { setDynamicSurfaceModule(null); setWorldClimate(null); setDynamicSurfaceMessage('') }
}, [repoLogin?.username, repoLogin?.domain])
const cloneCodeChannel = async (event: React.FormEvent) => {
event.preventDefault()
if (!cloneUrl.trim()) return
@ -1761,6 +1814,11 @@ function HoloLakeApp() {
{zpMessage && <p className="global-message">{zpMessage}</p>}
</section>
<section className="plain-panel"><header><div><h2></h2><p></p></div></header><div className="theme-options">{themes.map((choice) => <button className={theme === choice.id ? 'active' : ''} key={choice.id} type="button" onClick={() => setTheme(choice.id)}><i className={choice.id}/><span>{choice.name}</span></button>)}</div></section>
<section className="plain-panel climate-panel"><header><div><h2></h2><p></p></div><span className={worldClimate ? 'status-chip online' : 'status-chip'}>{worldClimate ? '真实投影已启用' : dynamicSurfaceModule?.installedState || '未启用'}</span></header>
{worldClimate ? <dl className="evidence-list"><div><dt></dt><dd>{worldClimate.timePhase}</dd></div><div><dt></dt><dd>{worldClimate.weatherKind === 'UNAVAILABLE' ? '不可用 · 未伪造' : worldClimate.weatherKind}</dd></div><div><dt></dt><dd>{domainDisplayName(worldClimate.activeDomain)} · </dd></div><div><dt> / </dt><dd>{worldClimate.cityExposed || worldClimate.coordinatesExposed ? '边界异常' : '不向界面暴露'}</dd></div><div><dt></dt><dd><a href={worldClimate.sourceAttributionUrl} target="_blank" rel="noreferrer">Open-Meteo</a> · </dd></div></dl> : <p className="boundary-note">广</p>}
<button className="secondary-button" type="button" disabled={dynamicSurfaceBusy} onClick={() => void (dynamicSurfaceModule?.installedState === 'ACTIVE' ? refreshDynamicSurfaceModule() : activateDynamicSurfaceModule())}>{dynamicSurfaceBusy ? '正在验收…' : dynamicSurfaceModule?.installedState === 'ACTIVE' ? '刷新现实气候' : '确认两项权限并启用'}</button>
{dynamicSurfaceMessage && <p className="global-message">{dynamicSurfaceMessage}</p>}
</section>
<section className="plain-panel"><header><div><h2></h2><p> HoloLake </p></div></header>{releaseCandidate ? <div className="release-summary"><b>HoloLake {releaseCandidate.version}</b><p>{releaseCandidate.notes}</p><button className="primary-button" onClick={() => void installUpdate()}></button></div> : <button className="secondary-button" disabled={systemBusy} onClick={() => void checkUpdate()}></button>}</section>
</div>
{systemMessage && <p className="global-message">{systemMessage}</p>}
@ -1792,6 +1850,7 @@ function HoloLakeApp() {
const enterpriseRelationshipPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && enterpriseEntry.relationship_confirmation?.decision !== 'CONFIRM')
const enterpriseResponsibilityPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && !enterpriseRelationshipPending && enterpriseEntry.responsibility_receipt?.decision !== 'ACCEPT')
const climateClasses = worldClimate ? ` climate-${worldClimate.weatherKind.toLowerCase()} phase-${worldClimate.timePhase.toLowerCase()}` : ''
if (!repoLogin) {
const resolvedDomain = zeroPoint?.resolvedDomain || ''
@ -1858,7 +1917,7 @@ function HoloLakeApp() {
}
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
return <div className={`official-world signed-in-world${motionAwake ? ' motion-awake' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
return <div className={`official-world signed-in-world${motionAwake ? ' motion-awake' : ''}${climateClasses}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
<LakeAtmosphere awake={motionAwake}/>
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button><button type="button" onClick={() => void signOutRepo()}>退</button></div></header>
<main className="world-scene signed-in-scene">

View file

@ -1168,6 +1168,14 @@ const ROUTES = {
"moduleNumber": "HLP-NIPC-MOD-0030",
"operationNumber": "HLP-NIPC-OP-0146",
"targetNumber": "HLP-NIPC-TGT-0030"
},
"get_world_climate": {
"protocolVersion": "HLP-NIPC-v1",
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
"channelNumber": "HLP-NIPC-CH-0002",
"moduleNumber": "HLP-NIPC-MOD-0031",
"operationNumber": "HLP-NIPC-OP-0147",
"targetNumber": "HLP-NIPC-TGT-0031"
}
} as const

View file

@ -563,6 +563,17 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.world-star { position: absolute; border-radius: 50%; background: var(--primitive-star-core); animation: world-star-breathe var(--star-duration) ease-in-out var(--star-delay) infinite; }
@keyframes world-star-breathe { 0%, 100% { opacity: .14; transform: scale(.8); } 50% { opacity: .86; transform: scale(1.15); } }
.world-mist { position: absolute; z-index: 1; left: 6%; right: 6%; top: 38%; height: 70px; filter: blur(12px); pointer-events: none; background: radial-gradient(60% 100% at 50% 50%, var(--primitive-mist-a), var(--primitive-mist-b) 55%, transparent 78%); }
.world-climate-veil { position: absolute; z-index: 1; inset: 44px 0 45px; pointer-events: none; opacity: 0; transition: opacity 1.4s ease, background 1.4s ease; }
.official-world.phase-dawn .world-climate-veil { opacity: .36; background: radial-gradient(90% 58% at 28% 12%, rgba(255, 185, 136, .24), transparent 68%); }
.official-world.phase-day .world-climate-veil { opacity: .24; background: radial-gradient(100% 62% at 50% 0, rgba(214, 232, 255, .26), transparent 70%); }
.official-world.phase-dusk .world-climate-veil { opacity: .34; background: radial-gradient(90% 58% at 72% 16%, rgba(255, 146, 112, .22), transparent 68%); }
.official-world.phase-night .world-climate-veil { opacity: .2; background: radial-gradient(90% 58% at 52% 8%, rgba(80, 98, 176, .2), transparent 72%); }
.official-world.climate-cloud .world-climate-veil,
.official-world.climate-fog .world-climate-veil { opacity: .5; background: radial-gradient(82% 24% at 52% 42%, color-mix(in srgb, var(--primitive-mist-a) 78%, transparent), transparent 76%), radial-gradient(70% 22% at 28% 61%, color-mix(in srgb, var(--primitive-mist-b) 65%, transparent), transparent 78%); }
.official-world.climate-rain .world-climate-veil,
.official-world.climate-storm .world-climate-veil { opacity: .58; background: radial-gradient(95% 48% at 50% 12%, rgba(40, 54, 86, .34), transparent 74%), radial-gradient(70% 22% at 50% 64%, color-mix(in srgb, var(--primitive-cool-glow) 20%, transparent), transparent 75%); }
.official-world.climate-snow .world-climate-veil { opacity: .55; background: radial-gradient(circle at 18% 22%, rgba(255,255,255,.3) 0 1px, transparent 2px), radial-gradient(circle at 68% 34%, rgba(255,255,255,.24) 0 1px, transparent 2px), radial-gradient(90% 42% at 50% 24%, rgba(226,238,255,.18), transparent 74%); background-size: 82px 74px, 113px 96px, auto; }
.climate-panel a { color: var(--accent-light); font-weight: 650; }
.world-shimmer { position: absolute; z-index: 2; inset: 39% 0 42px; overflow: hidden; pointer-events: none; }
.world-shine { position: absolute; width: 38%; height: 130px; border-radius: 50%; filter: blur(34px); animation: world-shine-glide 28s ease-in-out infinite alternate; }
.world-shine.cool { left: 6%; top: 12%; background: color-mix(in srgb, var(--primitive-cool-glow) 18%, transparent); }