Skip to content

Core Concepts

Previous: Quick Start · Next: Integration Overview

This page covers the architecture — for those who want to "understand it before configuring." After reading, you'll grasp: what exactly happens inside QS for a single keypress, why the state machine exists, why a skill has "two sets of fields," and what QS does and does not do.


🎯 The runtime pipeline: 7 stages of a single keypress

The core of QS is one fixed runtime pipeline. Whether the trigger comes from an item, a command, the API, or a passive event, it walks this same path:

text
   ① Input               Player keypress / command / API / passive event
      │                  Normalized into a standard trigger signal

   ② State machine       What state is it in now? Idle? Combo window? Locked?
      │  (State)         Decides whether this input gets in, and which path it takes

   ③ Graph resolution    In the current state, which node does this key hit?
      │  (Graph) + Combo  Is it a link in some combo sequence?

   ④ Execution plan      Assembles "which MM skill to cast, who the target is, which parameters to carry"
      │  (Plan)

   ⑤ Gate                Checks one by one: unlock? cooldown? charges? GCD? resource? blood sacrifice? conflict? conditions?
      │                  Any failure → blocked with a prompt, the pipeline stops here

   ⑥ Execution (Exec)    The MythicExecutor hands the plan to MythicMobs to perform


   ⑦ Post-processing     Start cooldown / change state / advance the combo / refresh the actionbar
      │  (Post)

💡 Why "one pipeline for all triggers"? This way, rules like cooldown, charges, combos, and targets are written only once and apply equally to "cast via item," "cast via command," and "cast via passive." You'll never hit the two-sets-of-logic conflict where "the command can cast but the item can't."

Remember the fixed order of this pipeline — the gate comes before execution. So "the MM skill is clearly written but won't cast" almost always gets stuck at step ⑤ the gate (not unlocked / on cooldown / not enough resources), not an MM problem.


🔄 The state machine: 6 states

QS maintains a skill state machine for each player. The point of its existence: to make time-sequenced things like "combos," "follow-ups," and "being silenced" decidable.

StateMeaning
IDLEDefault state; can normally open with a skill
CASTINGA skill is being cast
COMBO_WINDOWAfter a successful opener, a brief window opens; input within it counts as a "follow-up"
RECOVERYReserved state (recovery / stagger; no concrete logic enabled yet)
LOCKEDSilenced; no skill can be cast
INTERRUPTEDThe channel / cast was interrupted

When states transition

text
IDLE ──first keypress, opener succeeds──► CASTING
CASTING ──succeeds and the skill has a combo──► COMBO_WINDOW
COMBO_WINDOW ──combo_window_ms (default 800) elapses without a follow-up──► IDLE   (combo breaks)
Any state ──/qs silence N──► LOCKED ──after N seconds──► IDLE
Channel/cast interrupted by movement or damage ──► INTERRUPTED ──► IDLE

Key points:

  • Opener vs. follow-up is distinguished by state. The same right-click takes the opener node in IDLE and the follow-up node in COMBO_WINDOW (graph nodes use require_state to mark which is which). This is the underlying mechanism that makes combos possible.
  • The combo window times out. If you don't press the next link within combo_window_ms (default 800ms), the state returns to IDLE and the combo breaks.
  • Silenced = LOCKED. /qs silence 5 puts the player into LOCKED for 5 seconds, during which no skill can be cast; /qs silence 0 clears it. This is the implementation entry point for skill-lock / silence debuffs.

See Graphs and Combos and Cast Modes and Channeling.


📑 A skill's "two sets of fields": ecosystem validation vs. runtime reading

Open any skill yml and you'll see the fields split into two halves. This is a deliberate design in QS, and understanding it saves you from big pitfalls.

This set of fieldsPurposeWho reads it
meta / trigger / state / graph / executionEcosystem info — categorization, consistency validation on reload, combo orchestration referenceschema validator
type / cooldown / resource / cast_mode / levels / variablesActually read at runtime — truly decides behaviorruntime loader

A side-by-side example (from fire_wave):

yaml
meta:
  type: active          # Ecosystem tag: for categorization
trigger:
  primary: RIGHT_CLICK  # Ecosystem: declares the primary trigger key (compared against the graph during validation)
state:
  required: IDLE        # Ecosystem: declares the required state (compared against the entry node during validation)
execution:
  mythic_skill: fire_wave   # Ecosystem: declares which MM skill to execute

type: active            # ← What truly decides active/passive at runtime
mythic_skill: fire_wave # ← What's actually used at runtime (execution.mythic_skill takes priority)

⚠️ The two sets must stay consistent. For example, state.required: IDLE must match the graph entry node's require_state: IDLE; execution.mythic_skill must match the entry node's mythic_skill. /qs reload runs a consistency check and emits a schema warning if they don't match.

Why split into two sets? The upper set lets the ecosystem (the validator, combo orchestration, the future editor GUI) understand "what this skill looks like, which category it belongs to, whether it combos" without running the skill; the lower set is the config the engine actually consumes at runtime. One is for "understanding," the other for "executing."


🚪 The gate: ordered checkpoints before allowing a cast

The gate is step ⑤ of the pipeline and one of QS's most core values. It's a chain of checks executed in a fixed order; any failure blocks the skill and prompts the player:

CheckpointWhat it checks
UnlockHas this player unlocked this skill?
Cooldown / cooldown groupIs it on CD? Do skills in the same cooldown group share a CD?
ChargesAre there charge layers left (charge skills use layers instead of a binary cooldown)?
Global cooldown (GCD)Did you just cast another skill and you're still in the global cooldown?
ResourceIs there enough mana (temporary placeholder, moving to QC later)?
Health / hunger (blood sacrifice)Is there enough health/hunger to spend? Too low to cast?
Conflict groupDid you recently cast a skill in the same conflict group (gate.conflict_window_ms default 1000ms)?
Declarative conditionsAre all the conditions in conditions: satisfied?

💡 The gate is the "decision layer," not the "presentation layer." It only answers whether a cast can happen, never draws anything. So when a skill won't cast, think gate first (which checkpoint failed) — don't dive straight into MM to debug. For detailed checkpoint config see Cooldown, Charges, GCD, and Conflicts and Costs, Conditions, and Variables.


🌉 The bridge: QS → MythicMobs

After the gate passes, QS must hand the execution plan to MM. This handoff seam is called the bridge.

Bridge mode (config.yml's mythic.bridge_mode)Behavior
AUTO (recommended / default)Prefer registering the skill via the MM API → validate → on failure, fall back to writing YAML and loadSkills()
API_MODEAPI only, no fallback (for development / testing)
YAML_STUBOnly write to the MM skills folder + go through the load lifecycle

About the bridge, remember three things:

  1. It runs even without MM. The bridge enters placeholder mode, and triggering a skill → [QinhSkills] <skill name> in chat. Seeing it proves the QS side is fully working.
  2. Never overrides your MM skill of the same name. The placeholder only exists when "you haven't written a skill of the same name"; once you write a real skill, the bridge uses yours.
  3. /qs bridge shows the bridge status, diagnosing whether MM is present and whether registration succeeded.

See Integrating MythicMobs.


🧭 Responsibility boundaries: what QS does / doesn't do

Finally, nail down the boundary — this is key to understanding the entire Qinhuai ecosystem.

QS does: cast logic / trigger normalization / target acquisition / gating (unlock · cooldown · charges · cooldown group · GCD · conflict · resource · blood sacrifice · conditions · cast_mode · channel cast bar and interruption) / passing variable parameters through to MM / JS script hooks (pre_js / post_js).

QS does not do:

What it doesn't doWhose job it is
Particles / movement / presentation / the mechanic itselfMythicMobs
Damage value settlementAttribute plugins like AttributePlus (on the MM side)
Player class / player level / mana·stamina resource pools / cooldown reduction (CDR) / player attributesQinhClass (QC)

🔑 About mana: the resource.mana in a skill yml and the resources.default_mana/max_mana in config.yml are temporary placeholders before QC takes over. Resource spending goes through QS's existing player-profile seam (PlayerSkillProfile), not a separate pool; when QC takes over resources in the future, only this one spot changes — the skill yml doesn't need to.

This boundary follows directly from QS's first design philosophy: QS only manages logic — not presentation, not values, not player progression. Everyone does their own job.


Keep reading