import type { ResistanceLevel, ScalarValue } from '@ai-rpg-engine/core'; export type EntityBlueprint = { id: string; type: string; name: string; tags?: string[]; baseStats?: Record; baseResources?: Record; startingStatuses?: string[]; inventory?: string[]; equipment?: Record; aiProfile?: string; scripts?: string[]; /** * Structural copy of EntityState.relations (npc-agency trust, etc.). * Copied by entityBlueprintToState when present. */ relations?: Record; /** * Structural copy of EntityState.custom (companion role, personalGoal, …). */ custom?: Record; /** * Structural copy of EntityState.resistances (holy/fire/…). Unknown * level names are dropped at intake, not refused. */ resistances?: Record; /** * Structural copy of EntityState.faction (affiliationOf). Opaque string. */ faction?: string; /** * Structural copy of EntityState.ruleProfileId. Opaque string; combat * resolves it against WorldState.ruleProfiles after boot. */ ruleProfileId?: string; }; /** * The legacy flat target enum. Retained as the stable, required field so every * existing content pack, starter, and consumer (ability-core resolveTargets, * ability-summary distribution, ability-intent scoring) keeps working byte-for-byte. */ export type TargetType = 'self' | 'single' | 'zone' | 'all-enemies' | 'none'; /** How many entities the spec can reach (orthogonal to who/which — finding 8). */ export type TargetScope = 'self' | 'single' | 'all'; /** Which side relative to the source the spec selects (faction predicate — finding 9). */ export type TargetAffiliation = 'ally' | 'enemy' | 'any'; /** Liveness gate on candidates. Named `life` (not `filter`) to avoid colliding with * the pre-existing `filter: string[]` tag filter, which is load-bearing for callers. */ export type TargetLife = 'alive' | 'dead' | 'any'; /** * Ability targeting spec. * * Targeting decomposes into independent orthogonal axes — Scope × Affiliation × * Life (+ optional area) — per the design lock (finding 8: Liquid Fire tactics-RPG * Range×Area×EffectTarget; corroborated by GAS Spec/Filter split and the 5e SRD * valid-target contract). * * BACK-COMPAT: `type` (the flat enum) remains required and is the single source of * truth when the axes are omitted — old content `{ type: 'self' }` is mapped to * `{ scope:self, affiliation:ally, life:alive, includeSelf:true }` by * {@link normalizeTargetSpec}. New ally-support content sets the axes explicitly * (e.g. a heal = `{ type:'single', scope:'single', affiliation:'ally', life:'alive', includeSelf:true }`). * When both are present the axes win; `type` should be set to the nearest legacy * value so distribution/summary tooling stays meaningful. */ export type TargetSpec = { type: TargetType; range?: number; /** Tag filter — candidate must carry at least one of these tags. Unchanged. */ filter?: string[]; /** How many: self / single / all. Defaults from `type`. */ scope?: TargetScope; /** Whose side: ally / enemy / any. Defaults from `type`. */ affiliation?: TargetAffiliation; /** Liveness gate: alive / dead / any. Defaults to 'alive'. */ life?: TargetLife; /** Optional named area shape for `scope:'all'` (e.g. 'zone'). Reserved for future shapes; resolution treats any value as the source's zone today. */ area?: string; /** Whether the source is a valid candidate for an ally/any spec. Defaults from `type` (true only for legacy 'self'). */ includeSelf?: boolean; }; /** Fully-resolved axes — every field present. Output of {@link normalizeTargetSpec}. */ export type NormalizedTargetSpec = { scope: TargetScope; affiliation: TargetAffiliation; life: TargetLife; filter?: string[]; range?: number; area?: string; includeSelf: boolean; }; /** * Resolve a (possibly legacy/partial) {@link TargetSpec} to a fully-populated * {@link NormalizedTargetSpec}. Pure and deterministic — no RNG, no clock. * * Mapping of the flat `type` (used only as the default when an axis is absent): * - `self` → scope:self, affiliation:ally, life:alive, includeSelf:true * - `single` → scope:single, affiliation:enemy, life:alive, includeSelf:false * - `all-enemies` → scope:all, affiliation:enemy, life:alive, includeSelf:false * - `zone` → scope:all, affiliation:any, life:alive, includeSelf:false * - `none` → scope:self, affiliation:any, life:any, includeSelf:false * * Explicit axes always override the `type`-derived default, so new content composes * freely (e.g. an ally revive: `{ type:'single', affiliation:'ally', life:'dead' }`). */ export declare function normalizeTargetSpec(spec: TargetSpec): NormalizedTargetSpec; export type ResourceCost = { resourceId: string; amount: number; }; export type ConditionSpec = { type: string; params: Record; }; export type CheckDefinition = { stat: string; difficulty: number; onFail?: string; }; export type EffectDefinition = { type: string; target?: 'actor' | 'target' | 'zone'; params: Record; }; export type AbilityPresentation = { text?: string; hitText?: string; missText?: string; soundCue?: string; }; export type AbilityDefinition = { id: string; name: string; verb: string; tags: string[]; costs?: ResourceCost[]; target: TargetSpec; checks?: CheckDefinition[]; effects: EffectDefinition[]; cooldown?: number; requirements?: ConditionSpec[]; ui?: AbilityPresentation; }; export type DurationSpec = { type: 'ticks' | 'permanent' | 'conditional'; value?: number; condition?: ConditionSpec; }; export type ModifierDefinition = { stat: string; operation: 'add' | 'multiply'; value: number; }; export type TriggerDefinition = { event: string; condition?: ConditionSpec; effect: EffectDefinition; }; export type StatusPresentation = { icon?: string; color?: string; description?: string; }; export type StatusDefinition = { id: string; name: string; tags: string[]; stacking: 'replace' | 'stack' | 'refresh'; maxStacks?: number; duration?: DurationSpec; modifiers?: ModifierDefinition[]; triggers?: TriggerDefinition[]; removal?: ConditionSpec[]; ui?: StatusPresentation; }; export type TextBlock = { text: string; condition?: ConditionSpec; }; export type ExitDefinition = { targetZoneId: string; label?: string; condition?: ConditionSpec; }; /** * A party-state gate on entering a zone (C3/P2), as it crosses the wire. * * `conditions` are COMPILED `ConditionSpec`s. World Forge authors a * SpawnCondition-grammar string and compiles it at export — the ink pattern from * RG-C1 Lane 2 — so no author syntax reaches the engine. */ export type EntryGateDefinition = { conditions: ConditionSpec[]; mode: 'hard' | 'soft'; /** The authored "show the lock" message. */ reason?: string; }; export type ZoneDefinition = { id: string; name: string; tags?: string[]; description?: TextBlock[]; neighbors?: string[]; light?: number; noise?: number; hazards?: string[]; interactables?: string[]; entities?: string[]; exits?: ExitDefinition[]; /** C3/P2 — party-state gate on entry. Absent ⇒ ungated. */ entryGate?: EntryGateDefinition; /** * C3/P3 — ids into the pack's `hazardDefinitions`. The legacy free-text * `hazards: string[]` above is UNTOUCHED and stays inert without pack code; * that contrast is C0's finding and is preserved deliberately. */ hazardRefs?: string[]; /** * C3/P4 — the scene descriptor the client's diorama binds to. Stable keys only; * see `ZoneState.scene` for the constraint and its grounding. */ scene?: { biome?: string; timeOfDay?: string; dressingDensity?: 'sparse' | 'normal' | 'dense'; variantTags?: string[]; }; }; export type RoomDefinition = { id: string; name: string; zones: ZoneDefinition[]; tags?: string[]; ambientText?: TextBlock[]; hazards?: string[]; encounterTable?: string[]; exits?: ExitDefinition[]; scripts?: string[]; }; export type QuestStage = { id: string; name: string; description?: string; objectives?: string[]; triggers?: TriggerDefinition[]; nextStage?: string; failStage?: string; }; export type RewardDefinition = { type: string; params: Record; }; export type QuestDefinition = { id: string; name: string; stages: QuestStage[]; triggers?: TriggerDefinition[]; rewards?: RewardDefinition[]; failConditions?: ConditionSpec[]; }; export type DialogueChoice = { id: string; text: string; nextNodeId: string; condition?: ConditionSpec; effects?: EffectDefinition[]; }; export type DialogueNode = { id: string; speaker: string; text: string | TextBlock[]; choices?: DialogueChoice[]; effects?: EffectDefinition[]; nextNodeId?: string; }; export type DialogueDefinition = { id: string; speakers: string[]; nodes: Record; entryNodeId: string; }; export type ProgressionNode = { id: string; name: string; description?: string; cost: number; requires?: string[]; effects: EffectDefinition[]; }; export type ProgressionTreeDefinition = { id: string; name: string; currency: string; nodes: ProgressionNode[]; }; export type SoundCueDefinition = { id: string; trigger: string; channel?: 'ambient' | 'stinger' | 'voice' | 'ui'; priority?: 'low' | 'normal' | 'high'; condition?: ConditionSpec; tags?: string[]; }; /** * Data-side hazard record on a ContentPack (C3/P3). Structural shape of the * interpreter's HazardSpec; content-schema sits below modules so the closed * trigger/effect unions are not imported here. Runtime vocabulary lives in * @ai-rpg-engine/modules `hazard-interpreter`. */ export type HazardSpec = { id: string; name?: string; effects: unknown[]; trigger: string; moveCostDelta?: number; passable?: string; blocksVision?: boolean; weatherConditions?: string[]; immuneTags?: string[]; tags?: string[]; }; /** Alias matching ContentPack.hazardDefinitions commentary. */ export type HazardDefinition = HazardSpec; /** * Item catalog entry on a ContentPack. Structural subset of * `@ai-rpg-engine/equipment`'s ItemDefinition — content-schema sits below * equipment, so the pack field is typed here. A real equipment ItemDefinition * is assignable as-is. Runtime slot/rarity/loadout semantics live in that * package; this record is the authoring/load shape (`id` required). */ export type ItemDefinition = { id: string; name?: string; description?: string; slot?: string; rarity?: string; statModifiers?: Record; resourceModifiers?: Record; grantedTags?: string[]; grantedVerbs?: string[]; requiredTags?: string[]; provenance?: Record; }; //# sourceMappingURL=schemas.d.ts.map