On this page
In-memory authoritative state for PixelWeaver projects.
#server.src.pixelweaver.state
#server.src.pixelweaver.state
In-memory authoritative state for PixelWeaver projects.
The server is the single source of truth. All state mutations flow through ServerState, which is then persisted to disk by the auto-saver.
#PartRegistryEntry
One entry in the project-scoped part registry (mirrors PartDefinition).
Internal keys are snake_case; JSON emission (to_dict / patches) uses camelCase for JS consumers. The variant list is derived, never stored.
#VariantPreset
One variant preset (a named set of per-group palette swaps).
A faithful mirror of the client's serialized VariantPreset shape (variant-state.svelte.ts SerializedPreset): groupOverrides maps a layer-group id to a color map (original hex -> replacement hex). The stored shape is the client-canonical camelCase one -- like the manifest / level map, the raw dict is kept verbatim so it round-trips byte-exact. The active-preset selection is a transient client-only preview toggle and is NOT stored here.
#LevelTilePlacement
One tile placement / the active-tile brush (mirror of TilePlacement).
#LevelEntity
A placed entity on an entity layer (mirror of EntityPlacement).
#LevelCollisionPoint
One vertex of a collision shape's polygon (mirror of a client collision point).
#LevelCollisionShape
A collision rect/polygon (mirror of CollisionShape).
#LevelLayer
One map layer (mirror of SerializedLayer). The per-kind collections are optional -- a tile layer carries tiles, an entity layer entities, a collision layer collisionShapes (the client omits the others).
#LevelMap
The whole level-editor document (mirror of SerializedMapState).
NOTE: activeTile is deliberately absent. The selected brush is per-user tool state (like the client-local foreground color), NOT authoritative document state -- syncing one user's brush to everyone would be wrong. It lives only in the client map-state singleton and the client's own local project-save format; the server round-trip does not carry it. set_level_map strips it from any legacy save that still has it (see below).
#default_level_map
def default_level_map() -> dict[str, Any]The empty level document (mirror of the client's initial map-state).
Absent level_map on load resolves to this -- the migration for projects created before the level editor became server-authoritative. activeTile is client-local (see LevelMap) and is intentionally not present here.
#FrameState
Single animation frame within a canvas.
Each frame holds its own pixel data per layer. The id is a stable UUID that matches the frontend Frame.id. duration_ms=None means "derive from global_fps".
#CanvasState
State for a single canvas within a project.
#set_iso_settings
def set_iso_settings(self, tile_width: int, tile_height: int, footprint_cols: int, footprint_rows: int) -> NoneValidate and set this canvas's iso settings (all-or-nothing).
Hard errors (validate everything before setting anything): - tile_width/tile_height must be even integers >= 2. - footprint_cols/footprint_rows must be integers >= 1.
#current_frame
def current_frame(self) -> FrameStateReturn the currently selected frame (bounds-checked).
#frame_at
def frame_at(self, index: int) -> FrameStateReturn frame at the given index (bounds-checked).
#frame_count
def frame_count(self) -> intNumber of frames in this canvas.
#to_dict
def to_dict(self) -> dict[str, Any]Serialize to a JSON-compatible dict (excludes heavy pixel data).
#to_full_dict
def to_full_dict(self) -> dict[str, Any]Serialize including info about which layers have pixel data.
#ProjectState
State for a single project.
#set_variant_presets
def set_variant_presets(self, presets: list[dict[str, Any]] | None) -> NoneValidate and set the variant preset list (None/absent = empty list).
Structurally validates each entry against VariantPreset (raises pydantic ValidationError on malformed data -- a hard error, never a silent fallback), enforces unique ids, then stores the RAW dicts unchanged so the client-canonical shape round-trips byte-exact (mirror of set_level_map). None (an absent save field) resolves to the empty list -- the migration for pre-variant-authoritative projects.
#set_current_roles
def set_current_roles(self, foreground_role: int, background_role: int) -> NoneValidate and set the current foreground/background roles.
Roles must be integers in [0, 254] -- the 0xFF sentinel is never a current role. Hard error on invalid values (no silent defaults for present-and-invalid data); both values validate before either is set.
#set_level_map
def set_level_map(self, level_map: dict[str, Any] | None) -> NoneValidate and set the level-editor document (None = empty document).
Structurally validates against LevelMap (raises pydantic ValidationError on malformed data -- a hard error, never a silent fallback), then stores the RAW dict unchanged so the client-canonical shape round-trips byte-exact (mirror of set_manifest storing the raw manifest). None (an absent save field) resolves to the empty document -- the migration for pre-level-editor projects.
#set_manifest
def set_manifest(self, manifest: dict[str, Any] | None) -> NoneValidate and set the constraint manifest (None clears it).
Raises pydantic.ValidationError for invalid manifests -- a hard error, never a silent fallback.
#set_character_preview
def set_character_preview(self, doc: dict[str, Any] | None) -> NoneValidate and set the character-preview document (None clears it).
Raises pydantic.ValidationError for invalid documents -- a hard error, never a silent fallback (mirror of set_manifest).
#set_part_registry
def set_part_registry(self, entries: list[dict[str, Any]]) -> NoneValidate and set the part registry (mirror of set_manifest).
Validates each entry, then uniqueness of id/name/z_order across entries, then class resolution for every non-null palette_class_id against the current manifest. All hard errors -- never a silent fallback. Only assigns once every check passes.
#_part_registry_json
def _part_registry_json(self) -> list[dict[str, Any]]Serialize the part registry with camelCase keys for JS consumers.
#_set_members
def _set_members(self, part_set: str, except_name: str) -> list[CanvasState]Members of part_set OTHER than except_name.
#set_canvas_part_metadata
def set_canvas_part_metadata(self, canvas_name: str, part_set: str, part_name: str, variant_index: int, paired_with: str | None=None) -> NoneSet a canvas's part-set metadata with set-wide invariant checks.
Hard errors (no silent coercion): - variant_index must be a non-negative int; part_set/part_name non-empty. - paired_with (when given) must name an existing, different canvas. - all members of a set share dimensions (width/height) and origin (origin_x/origin_y). - two members may not share the same (part_name, variant_index).
#clear_canvas_part_metadata
def clear_canvas_part_metadata(self, canvas_name: str) -> NoneClear a canvas's four part fields (does not touch the partner).
#pair_canvases
def pair_canvases(self, name_a: str, name_b: str) -> NonePair two index-locked canvases in different sets reciprocally.
Hard errors: unknown names, name_a == name_b, either already paired, either variant_index None or unequal, either part_set None or equal.
#unpair_canvases
def unpair_canvases(self, name: str) -> NoneClear a canvas's pairing on both sides. Hard errors if not paired.
#to_dict
def to_dict(self) -> dict[str, Any]Serialize to a JSON-compatible dict for state sync.
#ServerState
In-memory authoritative state for all projects.
#create_project
def create_project(self, name: str, width: int, height: int) -> ProjectStateCreate a new project with a default canvas and layer.
#get_project
def get_project(self, name: str) -> ProjectState | NoneGet a project by name.
#get_active_project
def get_active_project(self) -> ProjectState | NoneGet the currently active project.
#set_active_project
def set_active_project(self, name: str) -> NoneValidate and set the active project by name.
The single validate-and-set path shared by the REST activate endpoint and the MCP open_project tool. Raises KeyError if no project with that name exists. This is navigation only -- it does not mutate project content, so callers do not mark the auto-saver dirty.
The active project IS persisted, but as a lightweight pointer (not project content): the collab-server navigation choke points (the REST activate endpoint, the create endpoint, and the MCP state-sync handler) write an active_project.json marker via storage.save_active_project_marker, and the lifespan startup restores it. This method stays pure (no I/O) so unit tests and the MCP-process ServerState can call it without touching the data dir.
#delete_project
def delete_project(self, name: str) -> boolDelete a project. Returns True if it existed.
#list_projects
def list_projects(self) -> list[str]Return names of all projects.
#_serialize_layer
def _serialize_layer(layer: dict[str, Any]) -> dict[str, Any]Serialize one layer node for a full-state patch, recursing into groups.
Matches the client's serialized layer shape exactly: a GROUP carries type: "group", a recursively-serialized children list, and an expanded flag; a LEAF carries the pixel/role metadata. Both kinds carry the common id/name/visible/opacity/blendMode/locked/offset fields.
#build_full_state_patch
def build_full_state_patch(project: ProjectState, *, replace: bool=False) -> dict[str, Any]Build a full-state patch from the current project state.
Serializes all canvas data including per-frame pixel buffers as base64 strings. Used when MCP modifies state and needs to notify WebSocket clients. Keys are camelCase for JS consumers.
replace is the EXPLICIT authoritative-replace signal. It is False for MCP mutations of the same project (the client MERGES the patch) and True only for a PROJECT SWITCH (the activate path), where the snapshot is the whole truth: the client rebuilds each canvas's layer tree, drops canvases the snapshot omits, and repoints the active canvas. Never a heuristic -- mode selection set by the caller.
#validate_part_sets
def validate_part_sets(project: ProjectState) -> list[str]Validate pairing completeness across all part-sets in a project.
Line-for-line port of the frontend validatePartSets (same message wording), operating on the server model where canvases are keyed by name and paired_with stores the partner's NAME.
Checks: - every paired_with target exists and pairs back (reciprocal-consistent) - paired canvases are index-locked (equal variant_index) - two sets connected by any pairing are index-complete