feat: admit signed dynamic world surface module
This commit is contained in:
parent
292889f934
commit
2b1f5d47fd
18 changed files with 584 additions and 14 deletions
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue