feat: initial commit

This commit is contained in:
2026-06-23 20:29:02 +08:00
commit ea8f6066c4
217 changed files with 60754 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
"""
Handoff Protocol (帧交接协议)
NovelMaster Core Engine - 状态管理器
特性:
- LOD (视锥剔除): 只精确交接在场人物
- Tick (全局时钟): 多线程悬置动作记录
- 休眠背景人物惰性更新
"""
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any
from enum import Enum
import time
class CharacterStatus(Enum):
ACTIVE = "active"
DORMANT = "dormant"
SUSPENDED = "suspended"
class ActionType(Enum):
DIALOGUE = "dialogue"
MOVEMENT = "movement"
COMBAT = "combat"
SKILL = "skill"
MENTAL = "mental"
class Urgency(Enum):
CRITICAL = "critical"
NORMAL = "normal"
LOW = "low"
@dataclass
class Vector3D:
"""3D坐标"""
x: float
y: float
z: float
@dataclass
class Character:
"""角色"""
id: str
name: str
status: CharacterStatus = CharacterStatus.ACTIVE
position: Optional[Vector3D] = None
last_update_tick: int = 0
pending_actions: List['ImminentAction'] = field(default_factory=list)
@dataclass
class ImminentAction:
"""悬置动作"""
id: str
character_id: str
type: ActionType
description: str
urgency: Urgency = Urgency.NORMAL
tick: int = 0
@dataclass
class WorldState:
"""世界状态"""
location: str
time_of_day: str
weather: Optional[str] = None
tension_level: int = 50 # 0-100
hook_pressure: int = 0 # 负债池压强
@dataclass
class HandoffMetadata:
"""交接元数据"""
chapter_number: int
scene_number: int
previous_chapter_summary: str
next_chapter_hint: Optional[str] = None
@dataclass
class HandoffFrame:
"""交接帧"""
tick: int
timestamp: float
active_characters: List[Character]
suspended_characters: List[Character]
imminent_actions: List[ImminentAction]
world_state: WorldState
metadata: HandoffMetadata
def cull_to_viewshed(characters: List[Character], view_center: Vector3D, radius: float) -> List[Character]:
"""LOD视锥剔除 - 只保留视野内角色"""
result = []
for char in characters:
if not char.position:
continue
distance = ((char.position.x - view_center.x) ** 2 +
(char.position.y - view_center.y) ** 2 +
(char.position.z - view_center.z) ** 2) ** 0.5
if distance <= radius:
result.append(char)
return result
def create_handoff_frame(
current_tick: int,
all_characters: List[Character],
world_state: WorldState,
metadata: HandoffMetadata
) -> HandoffFrame:
"""创建交接帧"""
active_characters = [c for c in all_characters if c.status == CharacterStatus.ACTIVE]
suspended_characters = [c for c in all_characters if c.status == CharacterStatus.SUSPENDED]
# 收集所有悬置动作
imminent_actions = []
for char in all_characters:
imminent_actions.extend(char.pending_actions)
return HandoffFrame(
tick=current_tick,
timestamp=time.time(),
active_characters=active_characters,
suspended_characters=suspended_characters,
imminent_actions=imminent_actions,
world_state=world_state,
metadata=metadata
)
def merge_handoff_frames(frames: List[HandoffFrame]) -> HandoffFrame:
"""合并多个交接帧"""
if not frames:
raise ValueError("No frames to merge")
latest_frame = max(frames, key=lambda f: f.tick)
return HandoffFrame(
tick=latest_frame.tick,
timestamp=latest_frame.timestamp,
active_characters=[c for f in frames for c in f.active_characters],
suspended_characters=[c for f in frames for c in f.suspended_characters],
imminent_actions=[a for f in frames for a in f.imminent_actions],
world_state=latest_frame.world_state,
metadata=latest_frame.metadata
)
def to_dict(frame: HandoffFrame) -> Dict[str, Any]:
"""序列化为字典"""
return {
"tick": frame.tick,
"timestamp": frame.timestamp,
"active_characters": [
{
"id": c.id,
"name": c.name,
"status": c.status.value,
"position": {"x": c.position.x, "y": c.position.y, "z": c.position.z} if c.position else None
}
for c in frame.active_characters
],
"suspended_characters": [c.id for c in frame.suspended_characters],
"imminent_actions": [
{
"id": a.id,
"character_id": a.character_id,
"type": a.type.value,
"description": a.description,
"urgency": a.urgency.value
}
for a in frame.imminent_actions
],
"world_state": {
"location": frame.world_state.location,
"time_of_day": frame.world_state.time_of_day,
"weather": frame.world_state.weather,
"tension_level": frame.world_state.tension_level,
"hook_pressure": frame.world_state.hook_pressure
},
"metadata": {
"chapter_number": frame.metadata.chapter_number,
"scene_number": frame.metadata.scene_number,
"previous_chapter_summary": frame.metadata.previous_chapter_summary,
"next_chapter_hint": frame.metadata.next_chapter_hint
}
}