Appearance
Faction System
Overview
The Faction System models organizations, groups, and power structures within a collaborative world. Factions represent any collective entity that characters can belong to -- vampire clans, noble houses, criminal syndicates, military orders, religious cults, or corporate conglomerates. They provide the organizational layer that sits above individual characters and below the world itself.
Factions support nested hierarchies (a clan contains sub-factions, a kingdom contains duchies), character memberships with ranks, and inter-faction relationships that capture alliances, rivalries, and political dynamics. A dedicated graph endpoint assembles faction nodes and relationship edges into a visualization-ready structure, giving Storytellers and players a political map of their world.
The Faction System is tightly coupled with the Character System. Characters join factions through memberships, and the faction graph can surface member counts and character groupings. Together, the two systems create a complete social and organizational model for any creative project.
Goals
- Allow Storytellers and Co-Creators to define factions with types, descriptions, and emblems
- Support nested faction hierarchies (parent-child) for modelling organizational structure
- Enable character-to-faction membership with optional rank/role
- Model inter-faction relationships with types, strength, and secrecy
- Expose a permission-filtered faction relationship graph endpoint
- Track how faction relationships evolve over time
- Integrate cleanly with the Character System for cross-feature queries
User Stories
Game Master / Storyteller
- As a Storyteller, I want to create factions representing the major power groups in my world so players understand the political landscape.
- As a Storyteller, I want to nest factions (e.g., "Camarilla" > "Ventrue Clan" > "Chicago Ventrue") so I can model organizational hierarchies.
- As a Storyteller, I want to define secret alliances between factions that only I and the Owner can see, so I can prepare political intrigue.
- As a Storyteller, I want to assign characters to factions with specific ranks (e.g., "Prince", "Sheriff", "Neonate") so the power structure is clear.
- As a Storyteller, I want to view the full faction relationship graph -- including secret relationships -- so I can plan storylines around political dynamics.
Writer / Co-Creator
- As a Co-Creator, I want to create factions and link characters to them so I can contribute to the world's organizational fabric.
- As a Co-Creator, I want to upload faction emblems/symbols so each faction has a visual identity.
- As a Co-Creator, I want to define relationships between factions (allied, enemy, vassal) so the political web is documented.
Player
- As a Player, I want to see which factions exist and who belongs to them so I understand the world's power structure.
- As a Player, I want to see the public faction relationship graph so I know which groups are allied or opposed.
- As a Player, I want to see my character's faction membership and rank so I understand my place in the world.
Functional Description
Faction CRUD
Factions are aggregate roots within the KnowledgeBase.Factions feature module. Each faction belongs to a single project and receives a project-scoped sequence number on creation (e.g., VNO-15).
Creation rules by role:
| Role | Can Create? | Can Edit? | Can Delete? |
|---|---|---|---|
| Owner | Yes | Any faction | Any faction |
| Storyteller | Yes | Any faction | Any faction |
| Co-Creator | Yes | Own factions | Own factions |
| Player | No | No | No |
| Viewer | No | No | No |
- Only Owner, Storyteller, and Co-Creator roles can create factions. Players and Viewers have read-only access.
- Co-Creators may only edit or delete factions they created.
- Deleting a faction cascades: all memberships, child faction references (parentFactionId set to null or children deleted -- see Edge Cases), and faction relationships involving the deleted faction are removed.
Faction Types
The type field categorizes factions at a high level:
| Type | Description | Examples |
|---|---|---|
| political | Governing bodies, ruling councils, noble houses | "The Camarilla", "House Stark", "Senate of Waterdeep" |
| military | Armed forces, mercenary companies, defence orders | "The Night's Watch", "Silver Fang Pack", "City Guard" |
| religious | Churches, cults, spiritual orders | "Church of the Unconquered Sun", "Cult of Ecstasy" |
| criminal | Underworld organizations, gangs, smuggling rings | "Giovanni Crime Family", "Thieves' Guild", "The Syndicate" |
| other | Any faction that does not fit the above categories | "The Mages' Academy", "Merchant Consortium" |
Faction Properties
| Property | Type | Description |
|---|---|---|
| name | string | Display name of the faction |
| description | string | Narrative description, purpose, history |
| type | enum | political, military, religious, criminal, other |
| symbolUrl | string? | URL to the faction's emblem or symbol image |
| parentFactionId | Guid? | Reference to parent faction for hierarchy |
| sequenceNumber | int | Project-scoped public ID |
Nested Factions (Hierarchy)
Factions support a parent-child hierarchy through the self-referencing parentFactionId field. This models real organizational structures where groups contain sub-groups.
Example hierarchy:
Hierarchy rules:
- A faction with
parentFactionId = nullis a top-level faction. - A faction with a valid
parentFactionIdis a child of that parent. - Maximum nesting depth: 5 levels. The system validates on creation and update that adding a parent would not exceed this limit. This prevents runaway recursion and keeps traversal queries performant.
- A faction cannot be its own parent (self-referencing loop detection).
- A faction cannot be set as a child of one of its own descendants (cycle detection).
- Moving a faction to a new parent is supported via the update endpoint by changing
parentFactionId.
Hierarchy traversal:
- The faction list endpoint returns all factions with their
parentFactionIdpopulated, allowing the frontend to assemble the tree. - The faction detail endpoint includes a
childrenarray of immediate child factions for convenience. - Full ancestor/descendant traversal uses recursive CTE queries when needed (e.g., "show all factions under the Camarilla").
Faction Memberships
Memberships link characters to factions. A character can belong to multiple factions simultaneously, and each membership can carry an optional rank or role title.
Membership Properties
| Property | Type | Description |
|---|---|---|
| factionId | Guid | FK to the faction |
| characterId | Guid | FK to the character |
| rank | string? | Freeform rank/role title within the faction |
Membership Examples
| Character | Faction | Rank |
|---|---|---|
| Marcus Vitel | Ventrue Clan | Prince |
| Elena Vasquez | Toreador Clan | Harpy |
| The Baron | Camarilla | Justicar |
| The Baron | Chicago Ventrue | Patron |
In this example, "The Baron" belongs to two factions with different ranks in each.
Membership CRUD
| Endpoint | Description |
|---|---|
GET /api/projects/{key}/factions/{seq}/memberships | List all members of a faction |
POST /api/projects/{key}/factions/{seq}/memberships | Add a character to the faction |
PUT /api/projects/{key}/factions/{seq}/memberships/{id} | Update rank/role |
DELETE /api/projects/{key}/factions/{seq}/memberships/{id} | Remove character from faction |
Permission rules for memberships:
| Role | Can Add Members? | Can Update Rank? | Can Remove Members? |
|---|---|---|---|
| Owner | Yes | Yes | Yes |
| Storyteller | Yes | Yes | Yes |
| Co-Creator | To factions they created | On factions they created | From factions they created |
| Player | No | No | No |
| Viewer | No | No | No |
- A character can only be added to a faction once (unique constraint on factionId + characterId).
- The character must be visible to the user performing the operation. Private characters cannot be added to factions by users who cannot see them.
Faction Relationships
Faction relationships model the political and social dynamics between organizations. They follow a similar pattern to character relationships but with faction-specific semantics.
Relationship Types
| Type | Description | Example |
|---|---|---|
| allied | Formal or informal alliance | Ventrue and Toreador clans cooperating |
| enemy | Open hostility or opposition | Camarilla vs. Sabbat |
| neutral | No strong relationship | Two clans with no interaction |
| trade | Economic or resource exchange | Merchant guild supplying the military |
| vassal | Subordination or fealty relationship | A barony sworn to a kingdom |
| custom | User-defined relationship type | Any specific dynamic not covered above |
Relationship Properties
| Property | Type | Description |
|---|---|---|
| sourceFactionId | Guid | FK to the source faction |
| targetFactionId | Guid | FK to the target faction |
| type | enum | allied, enemy, neutral, trade, vassal, custom |
| strength | int (1-5) | Intensity of the relationship |
| isSecret | bool | If true, visible only to Storyteller and Owner |
| description | string? | Narrative context for the relationship |
Secret Relationships
The isSecret flag provides a simpler visibility model than character relationships (which use a full public/private visibility enum). When isSecret = true:
- The relationship is visible only to users with the Storyteller or Owner role.
- Co-Creators, Players, and Viewers cannot see the relationship in list endpoints or in the faction graph.
- This enables Storytellers to document hidden alliances, secret enmities, or behind-the-scenes political manoeuvring that players should not know about.
Faction Relationship Permission Rules
| Role | Can Create? | Can View? |
|---|---|---|
| Owner | Yes | All (including secret) |
| Storyteller | Yes | All (including secret) |
| Co-Creator | Yes | Non-secret only |
| Player | No | Non-secret only |
| Viewer | No | Non-secret only |
Historical Relationship Changes
Faction relationships evolve over time. The system supports this through two mechanisms:
Direct updates: A faction relationship's type, strength, or description can be updated via the PUT endpoint. The version history system (see Version History & Changelog) records each change, creating an auditable timeline of how the relationship evolved.
End and replace: For dramatic shifts (e.g., allies becoming enemies), the existing relationship can be deleted and a new one created. Both actions are captured in the activity feed and version history.
The version history integration means that even though the faction relationship entity itself stores only the current state, the full evolution is recoverable from the audit trail.
Faction Relationship Graph Endpoint
GET /api/projects/{key}/graphs/factions
This endpoint returns a pre-assembled graph of factions (nodes) and their inter-faction relationships (edges), optimized for frontend visualization.
Graph Response Structure
Node properties:
| Field | Description |
|---|---|
| id | Faction sequence number |
| name | Faction name |
| type | Faction type (political, military, etc.) |
| symbolUrl | Faction emblem URL (nullable) |
| parentId | Parent faction sequence number (nullable) |
| memberCount | Number of characters in this faction |
| members | Array of member summaries (character name, rank) |
Edge properties:
| Field | Description |
|---|---|
| id | Faction relationship internal ID |
| source | Source faction sequence number |
| target | Target faction sequence number |
| type | Relationship type (allied, enemy, etc.) |
| strength | 1-5 weight for edge thickness |
| isSecret | Whether this is a secret relationship (included only for authorized users) |
Graph Data Flow
Faction-Character Integration
The Faction System and Character System are closely related. Key integration points:
- Membership links: FactionMembership references both a Faction (from this module) and a Character (from the Characters module). The Factions module depends on the Characters module for character lookups.
- Cascade on character deletion: When a character is deleted, all their faction memberships are removed. This is handled via a domain event from the Character System or a direct cascade rule.
- Cascade on faction deletion: When a faction is deleted, all memberships within that faction are removed, and any child factions have their
parentFactionIdset to null (orphaned to top-level) or are recursively deleted (configurable -- see Edge Cases). - Visibility alignment: The faction graph filters out memberships for private characters that the requesting user cannot see. The member list for a faction only includes characters the user has permission to view.
- Cross-feature queries: A character's detail response can include a summary of their faction memberships. A faction's detail response includes its member list. These cross-feature queries use read-only access patterns.
Data Flow
Faction Creation Flow
Membership Management Flow
Diagrams
Faction Membership Data Model
Cross-System Interaction: Factions and Characters
Key Components
FactionsController
Handles faction CRUD endpoints. Routes follow the pattern /api/projects/{key}/factions/{seq}. Supports listing with optional parent filtering to retrieve subtrees.
MembershipsController
Handles faction membership CRUD endpoints. Routes are nested under the faction: /api/projects/{key}/factions/{seq}/memberships/{id}.
FactionRelationshipsController
Handles inter-faction relationship CRUD. Routes follow /api/projects/{key}/factions/relationships/{id}.
FactionGraphController
Handles the GET /api/projects/{key}/graphs/factions endpoint. Assembles the node-and-edge graph with member counts, member summaries, and permission-filtered edges.
FactionService
Core business logic for faction lifecycle: creation (with hierarchy validation and sequence number allocation), updates (with cycle detection for parent changes), and deletion (with cascade handling for memberships, child factions, and relationships).
MembershipService
Business logic for managing character-faction memberships: adding members (with duplicate detection and character visibility checks), updating ranks, and removing members.
FactionRelationshipService
Business logic for inter-faction relationships: creation, updates, deletion, and secrecy filtering.
IPermissionService (shared)
Consulted by all handlers. For the Faction System specifically, it determines whether a user can see secret faction relationships and whether a Co-Creator has ownership of a particular faction.
Feature Interactions
| Feature | Interaction |
|---|---|
| Character System | Faction memberships reference characters. Character deletion cascades to remove memberships. The faction graph includes character member data. See Character System. |
| Project Management | Factions belong to a project. Project membership and roles drive permission checks. Faction creation increments the project's shared sequence counter. |
| Permission & Visibility | All faction operations go through IPermissionService. Secret faction relationships are filtered for non-Storyteller/Owner roles. Membership lists filter out private characters. See Permission & Visibility. |
| Real-time & Collaboration | Faction creation, membership changes, and relationship changes raise domain events for the activity feed and SignalR notifications. See Real-time & Collaboration. |
| Version History | Faction edits, membership changes, and relationship changes are tracked for audit and rollback. Historical relationship evolution is recoverable from this trail. See Version History & Changelog. |
| Search & Navigation | Factions are indexed for project-wide search by name, type, and description. See Search & Navigation. |
Use Cases
Vampire: The Masquerade -- Clan Politics
A Storyteller running a Vampire campaign creates the major sects as top-level factions (Camarilla, Sabbat, Anarchs), then nests clans within each sect, and further nests city-level coteries within clans. Characters are assigned to their clan with ranks like "Prince", "Primogen", or "Neonate". Inter-faction relationships map alliances, enmities, and secret deals between clans. The faction graph reveals the political web that drives the chronicle.
Fantasy Noble Houses
A writer building a fantasy world creates noble houses as factions (House Stark, House Lannister) of type political. Vassal relationships model fealty. Allied and enemy relationships capture shifting alliances. Characters are assigned with ranks like "Lord", "Heir", "Knight". Nested factions represent cadet branches or sworn bannermen.
Urban Gang Territories
A Co-Creator modelling a modern urban setting creates gangs as criminal factions. Trade relationships represent drug supply chains. Enemy relationships mark turf wars. Characters hold ranks like "Boss", "Lieutenant", "Soldier". Secret alliances between supposedly rival gangs add layers of intrigue visible only to the Storyteller.
Corporate Factions
A writer modelling a cyberpunk setting creates megacorporations as factions of type other or political. Nested factions represent divisions and subsidiaries. Trade and vassal relationships model corporate hierarchies and partnerships. Characters hold ranks like "CEO", "Division Head", "Field Agent".
Edge Cases & Error Handling
| Scenario | Handling |
|---|---|
| Creating a faction with a non-existent parentFactionId | Return 404 Not Found for the parent faction. |
| Nesting exceeds 5 levels deep | Return 422 Unprocessable Entity with a message about the depth limit. |
| Setting a faction as its own parent | Return 422 Unprocessable Entity. Validate on create and update. |
| Creating a cycle in the hierarchy (A > B > C > A) | Detect cycles by traversing ancestors before updating parentFactionId. Return 422 if a cycle would result. |
| Deleting a faction that has child factions | Set children's parentFactionId to null, promoting them to top-level factions. Log a warning in the response that child factions were orphaned. |
| Deleting a faction with existing memberships | Cascade delete all memberships. The characters themselves are unaffected. |
| Deleting a faction with existing relationships | Cascade delete all faction relationships where this faction is source or target. |
| Adding a character to a faction they already belong to | Return 409 Conflict. Unique constraint on (factionId, characterId). |
| Adding a private character to a faction (user cannot see the character) | Return 404 Not Found for the character. Do not reveal the character exists. |
| Faction graph with secret relationships for a Player | Secret relationship edges are omitted from the graph. The graph appears as if those relationships do not exist. |
| Faction membership references a character that is later made private | The membership still exists in the database, but the member is omitted from membership lists and graph member counts for users who cannot see the character. |
| Co-Creator tries to edit a faction they did not create | Return 403 Forbidden. Co-Creators can only modify their own factions. |
| Concurrent sequence number allocation | Handled by PostgreSQL atomic UPDATE ... RETURNING on the project's counter. |
Phase & Priority
| Aspect | Value |
|---|---|
| Phase | Phase 2: Relationship Mapping |
| Priority | High -- factions are a core part of Phase 2 alongside characters |
| Dependencies | Requires Phase 1 (Project Management, Auth) and the Character System (for memberships) |
| Dependents | Collaboration features (comments on factions), Search (faction indexing) |