Files
CyberGril/tests/test_projection.py
mobai 1012d8bb79 chore: project foundation
Contracts (the shared language):
- percept / state / context / utterance / render / audio / capabilities
- Capability model instead of vendor schemas (see below)

Providers (capability-based abstraction):
- LLM + Embedding: OpenAI-compatible shape IS the standard, adopted directly
- ASR: internal contract is an event stream (partial -> final), because
  /v1/audio/transcriptions has no partial and dialogue needs it for
  endpointing. OpenAI adapter degrades to supports_partial=False.
- TTS: internal contract carries tags/emotion/style, because
  /v1/audio/speech has no paralinguistics and [laughter]/[breath] are
  the core of feeling alive. Unsupported providers are stripped AND
  reported via Degradation -- silent degradation is forbidden.
- Avatar: no de-facto standard, custom RenderCmd protocol driven by State.

Core:
- mood: dynamical system, slow variables, negativity bias (grudge),
  numbing after prolonged negativity
- bazi: four pillars -> numeric vector; 'ming' is data, not prose
- projection: phi, system event -> in-world statement; uncovered events
  are counted as projection gaps

Storage:
- SQLite schema, three memory layers, dyad ledger
- accessibility floor 0.03 (not 0): forgettable but still there

Config split:
- models.yaml is the ONLY place model versions live
- character.yaml holds world rules / emotion params, no version numbers

58 tests pass; ruff clean.
2026-10-19 20:08:52 +08:00

68 lines
1.9 KiB
Python

"""投影层测试。
守的是本体 C-膜 的落地机制:
未覆盖的事件必须落到 fallback,并**计数**。
projection_gap 的行数就是"现实感泄漏"的度量。
"""
from __future__ import annotations
from cybergril.core.projection import (
FALLBACK,
Projector,
violates_absence_rule,
)
def test_known_event_projects() -> None:
p = Projector()
assert p("process.wake") == "刚醒。"
assert p.gap_count == 0
def test_unknown_event_falls_back_and_counts() -> None:
gaps = []
p = Projector(on_gap=gaps.append)
out = p("totally.unknown.event", {"x": 1})
assert out == FALLBACK
assert p.gap_count == 1
assert gaps[0].event_key == "totally.unknown.event"
def test_register_fills_gap() -> None:
p = Projector()
p("new.event")
assert p.gap_count == 1
p.register("new.event", "这个我说得上来了。")
assert p("new.event") == "这个我说得上来了。"
# 计数不因补表而回退
assert p.gap_count == 1
def test_custom_table_overrides() -> None:
p = Projector({"process.wake": "醒了。"})
assert p("process.wake") == "醒了。"
def test_no_secrets_leak_into_projection() -> None:
"""投影只输出世界内陈述,不输出技术细节。"""
p = Projector()
forbidden = ["进程", "服务", "错误码", "API", "超时", "token"]
for key in p.known_keys():
text = p(key)
for f in forbidden:
assert f not in text, f"{key} 的投影泄漏了技术词 {f!r}"
def test_absence_rule_detects_forbidden_patterns() -> None:
assert violates_absence_rule("我一直在等你,等了好久")
assert violates_absence_rule("你会把我删掉吗")
assert not violates_absence_rule("这几天我这边发生了点事")
def test_all_default_projections_are_nonempty() -> None:
p = Projector()
for key in p.known_keys():
assert p(key).strip(), f"{key} 的投影为空"