Pre-authored characters have a ceiling. You can write a hundred NPCs and then you have a hundred NPCs, and players will see through the repetition the moment they find the pattern. The trait pool synthesis system we built for Lumenfall treats character personalities as combinatorial constructions rather than authored scripts. This post covers the design, the edge cases, and where the approach breaks down.
What a Trait Pool Is and Is Not
A trait pool is a vocabulary of character attributes that combine to produce behavioral profiles. The pool is not a script library. No trait encodes specific dialogue or specific response rules. A trait encodes a disposition: how a character weights certain types of information, how it approaches unfamiliar situations, what it values when outcomes are uncertain.
The distinction matters because scripts are exhaustible. You can read every line a character has. A disposition is not exhaustible in the same way. It applies to every situation the generation system produces, including situations the authors never specifically anticipated. The generative dialogue system reads the disposition profile and produces contextually appropriate responses. The trait pool sets the disposition. The dialogue system generates the expression of it.
We have 47 traits in the current pool. They are organized into six categories: social orientation (how a character approaches groups and strangers), knowledge posture (how a character treats information), material values (what a character wants in terms of resources), threat response (how a character behaves under pressure), temporal outlook (how much a character weighs past versus future), and occupational identity (what a character defines itself through in terms of work and role).
How Characters Get Built
Every NPC in Lumenfall is generated from a selection of five to nine traits drawn from the pool. The selection is not random across the full 47. It is constrained by two things: the zone context the character inhabits, and compatibility rules between traits.
Zone context weighting was described in the LLM personality profiles post from March. The short version: terrain type influences which traits are upweighted in the selection probability. High-hostility terrain environments upweight threat-response traits. Resource-scarce zones upweight material values traits that reflect scarcity. The world shapes who lives in it without encoding specific characters.
Trait compatibility rules prevent combinations that produce incoherent personalities. Not unrealistic ones, incoherent ones. A character can believably be highly suspicious and highly sociable; that combination produces a specific kind of guarded gregariousness that is interesting and consistent. A character cannot be both extremely high in territorial material values and extremely high in generosity disposition without a conflict that the behavioral system cannot resolve cleanly. The compatibility matrix has 47 by 47 entries, and about 11 percent of pairings have a constraint applied.
The constraints are soft rather than hard. A soft constraint reduces the probability of a pairing being selected rather than preventing it outright. This allows edge-case personalities to exist in the world without being common. Unusual trait combinations produce unusual characters. We want some of those. We just do not want the world to be evenly distributed across all possible combinations.
Behavioral Resolution: From Trait to Response
At runtime, the behavioral resolution system takes a trait profile and an interaction context and produces a character response. The context includes the player's faction relationship with the character (a simplified relationship state, not a full reputation system), the terrain zone they are in, the current weather state, and any conversation history from the current session.
The resolution works through a priority ordering of traits. Not all traits are equally active in all contexts. A character's threat-response traits become primary when the interaction context signals danger. Temporal outlook traits become primary when the interaction involves promises or commitments. The priority ordering is not fixed globally; it is computed from context at resolution time.
This produces behavior that players have described as feeling considered without feeling scripted. The character is not pulling from a decision tree the player can map. It is resolving a profile against a context, and the resolution is different each time the context is different. The appearance of thinking is not a performance. The system is actually doing contextual resolution rather than pattern-matching to a predetermined output.
The Edge Cases That Produce Bad Outcomes
Two failure modes appear with enough regularity to be worth naming.
The first is trait suppression, where a high-weight trait so dominates behavioral resolution that the character effectively has only one mode. A character with a very high threat-response trait value and a high-hostility zone context can end up resolving every interaction through the lens of threat, even when the context is neutral. The character is not broken; it is an extreme personality reading every situation as more dangerous than it is. The game is producing a paranoid character. Sometimes that is the right outcome. In practice, it makes those characters almost entirely non-interactive because they do not produce useful responses. We added a minimum weight floor that prevents any single trait from dominating more than 60 percent of behavioral resolution for interactions below a threat threshold. The floor is arbitrary but the problem it solves is real.
The second failure mode is combinatorial blandness. In the center of the trait probability distribution, the most likely character outcomes are also the most generic. Moderate social orientation, moderate knowledge posture, moderate material values produces a character that is not incoherent but is also not memorable. Mathematically this makes sense: the high-probability zone of any combinatorial system is the middle. Practically it means the modal character in Lumenfall is fine but unremarkable.
Our response to this was to add outlier seeding. Each zone is guaranteed at least one character whose trait selection was drawn from the low-probability tail of the distribution. These are the unusual characters: the ones with the difficult combinations, the extreme weights, the dispositions that are unexpected for the terrain context. They exist specifically to break the blandness of the probability center. Whether we have the right density of outlier characters is something we are still calibrating.
What the System Cannot Do
Characters with trait pool profiles cannot grow. The profile is set at generation time and does not change based on what happens during a session. A character who witnesses something dramatic does not shift in personality as a result. The trait profile that shapes their responses does not update. This is the hardest limitation to live with because character growth in response to events is a fundamental part of how fictional characters feel real.
We are not claiming we can solve this without significantly more architecture than we currently have. Session-persistent state for all NPCs in a world with potentially thousands of generated characters is a substantial storage and retrieval problem, not just a design one. The current honest position is that Lumenfall's characters have fixed personalities and consistent behavioral expression of those personalities. They do not have arcs. For the kind of game Lumenfall is, whether the absence of arcs is a serious design failure or an acceptable tradeoff depends on what a player is asking the world to give them. For some players it will matter a lot. For others the variety and coherence of the trait-generated population will be sufficient. We genuinely do not know which group is larger yet.