cns

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Parse turns MUGEN/Ikemen GO .cns text into StateDefs — the read-path entry point for this package.

Package cns defines the pure-data model for MUGEN/Ikemen GO combat logic (.cns) files: StateDef and Controller.

This is the read-path surface — the stable vocabulary a library consumer (editor, engine) works with. It carries no INI/expression parsing, file I/O, or write-only (format-preservation) logic; per CLAUDE.md's read/write separation constraint, that lives elsewhere so importing this data model alone never pulls in write-only dependencies.

This is deliberately minimal scaffolding, not a full CNS expression engine: a Controller's trigger conditions and parameters are stored as unevaluated data (plain strings and a string-to-string map), not resolved or type-checked against MUGEN/Ikemen's trigger expression language. See .vibe/decisions/011-cns-controller-parameters-are-untyped-key-value-data.md. StateDef's own typed numeric header fields carry the same escape hatch, one layer up: HeaderExprs holds the raw source text for one of those fields whenever it held a trigger expression rather than a literal integer. See .vibe/decisions/023-statedef-numeric-header-fields-unevaluated-expression-escape-hatch.md.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Serialize

func Serialize(w io.Writer, states []StateDef) error

Serialize writes states to w as MUGEN/Ikemen GO .cns text, emitting one "[Statedef N]" block per StateDef in order, each followed by its "[State N]" controller blocks in order.

This is a first-pass write path: it does not attempt a byte-exact round-trip of any original file's formatting, comments, or unrecognized sections (a separate, format-preserving concern — see Document) — it only guarantees valid, readable output that Parse reads back into an equivalent []StateDef. Every recognized Statedef header field is always written, even at its zero value, since a StateDef cannot distinguish "not present in the original file" from "explicitly set to the zero value" — omitting zero-value fields would be no more faithful and would only add complexity. A Controller's Triggers are written as "trigger1", "trigger2", ... in slice order; Parse only ever appends to Triggers by a "trigger"-prefixed key without recording which one was used (see .vibe/decisions/011-cns-controller-parameters-are-untyped-key-value-data.md), so this always reparses back into the same Triggers regardless of which key names were written. Parameters (an unordered map) are written sorted by key for deterministic, reviewable output; map order carries no meaning Parse relies on. A "[State N]" block's N is always its enclosing StateDef's Number — Parse discards this number entirely (see .vibe/decisions/012-cns-parse-header-detection-strategy.md), so any value reparses identically.

Types

type Controller

type Controller struct {
	// Type is the controller type ("type" parameter of the [State N]
	// block, e.g. "ChangeState", "VelSet", "HitDef").
	Type string `json:"type"`
	// Triggers are this controller's trigger condition expressions
	// ("trigger1", "trigger2", ... lines), verbatim and unevaluated, in
	// file order. A nil or empty Triggers means the controller has no
	// trigger lines at all — it runs unconditionally whenever its state
	// is active, not "never runs".
	Triggers []string `json:"triggers"`
	// Parameters are this controller's remaining key/value parameters
	// (every non-trigger line of the block), verbatim and unevaluated,
	// keyed by parameter name.
	Parameters map[string]string `json:"parameters"`
}

Controller is a .cns [State N] block: a single state controller, stored as unevaluated data rather than resolved or type-checked. A controller's effect (e.g. which state to transition to for a "ChangeState" controller) is just another entry in Parameters, exactly like any other controller type's parameters — see the package doc comment.

type Document

type Document struct {
	StateDefs []StateDef
	// contains filtered or unexported fields
}

Document is the write-path counterpart to Parse/Serialize: it exists so a .cns file can be round-tripped — parsed, then serialized back out — without losing the comments, block ordering, and unrecognized sections that the pure-data StateDef/Controller model deliberately does not carry (see .vibe/decisions/012-cns-parse-header-detection-strategy.md), mirroring air.Document/def.Document (see .vibe/decisions/003-air-round-trip-via-separate-document-type.md).

Document.StateDefs is decoded the same way Parse's return value is, for convenient structured access to what was parsed — but Serialize does not read it back. As long as Document.StateDefs is left untouched, ParseDocument followed by Serialize reproduces the original source byte-for-byte, comments and all. Mutating StateDefs has no effect on Serialize's output: regenerating text from an edited StateDefs slice while still preserving unrelated comments/sections/ordering around the edit is a heavier per-line reconciliation this type does not attempt.

func ParseDocument

func ParseDocument(r io.Reader) (*Document, error)

ParseDocument reads MUGEN/Ikemen GO .cns combat logic text from r, decoding it the same way Parse does while also retaining the exact source bytes needed for a faithful round trip through Serialize.

func (*Document) Serialize

func (d *Document) Serialize(w io.Writer) error

Serialize writes the Document's retained source back out to w verbatim, reproducing the exact text ParseDocument read — including comments, block ordering, unrecognized sections, and original line endings.

type MoveType

type MoveType string

MoveType is a .cns [Statedef N] block's "movetype" parameter: whether this state represents an attack, an idle/neutral action, or a hit reaction, used by other characters' trigger conditions (e.g. "MoveType = H" to detect being hit).

const (
	// MoveTypeAttack is an attacking state ("A").
	MoveTypeAttack MoveType = "A"
	// MoveTypeIdle is an idle/neutral state ("I").
	MoveTypeIdle MoveType = "I"
	// MoveTypeHit is a hit-reaction state ("H").
	MoveTypeHit MoveType = "H"
	// MoveTypeUnchanged leaves the character's current move type
	// unchanged when entering this state ("U").
	MoveTypeUnchanged MoveType = "U"
)

type PhysicsType

type PhysicsType string

PhysicsType is a .cns [Statedef N] block's "physics" parameter: which built-in physics (gravity, friction, air drag) apply while this state is active.

const (
	// PhysicsStanding applies standing physics ("S").
	PhysicsStanding PhysicsType = "S"
	// PhysicsCrouching applies crouching physics ("C").
	PhysicsCrouching PhysicsType = "C"
	// PhysicsAir applies airborne physics ("A").
	PhysicsAir PhysicsType = "A"
	// PhysicsNone applies no built-in physics ("N").
	PhysicsNone PhysicsType = "N"
	// PhysicsUnchanged leaves the character's current physics unchanged
	// when entering this state ("U").
	PhysicsUnchanged PhysicsType = "U"
)

type StateDef

type StateDef struct {
	// Number is the state number this block defines (the N in
	// [Statedef N]).
	Number int `json:"number"`
	// Type is the state's classification ("type" parameter).
	Type StateType `json:"type"`
	// MoveType is the state's move classification ("movetype" parameter).
	MoveType MoveType `json:"moveType"`
	// Physics is the built-in physics applied while this state is active
	// ("physics" parameter).
	Physics PhysicsType `json:"physics"`
	// Anim is the animation number played on entering this state ("anim"
	// parameter); 0 means "not set", in which case MUGEN/Ikemen defaults
	// it to Number.
	Anim int `json:"anim"`
	// Ctrl reports whether the player has control while this state is
	// active ("ctrl" parameter).
	Ctrl bool `json:"ctrl"`
	// PowerAdd is the power meter gain applied on entering this state
	// ("poweradd" parameter).
	PowerAdd int `json:"powerAdd"`
	// Juggle is the juggle points this state costs to use against an
	// already-airborne opponent ("juggle" parameter).
	Juggle int `json:"juggle"`
	// FaceP2 reports whether the character turns to face the opponent on
	// entering this state ("facep2" parameter).
	FaceP2 bool `json:"faceP2"`
	// HitDefPersist reports whether an active hit definition survives
	// into this state instead of being cleared ("hitdefpersist"
	// parameter).
	HitDefPersist bool `json:"hitDefPersist"`
	// MoveHitPersist reports whether "MoveHit"-triggered conditions
	// survive into this state instead of being cleared
	// ("movehitpersist" parameter).
	MoveHitPersist bool `json:"moveHitPersist"`
	// HitCountPersist reports whether the hit counter survives into this
	// state instead of being reset ("hitcountpersist" parameter).
	HitCountPersist bool `json:"hitCountPersist"`
	// SprPriority is the sprite drawing priority (layering order) for
	// this state ("sprpriority" parameter).
	SprPriority int `json:"sprPriority"`
	// HeaderExprs holds the raw, unevaluated source text of a numeric
	// header field ("anim", "poweradd", "juggle", "sprpriority") or
	// boolean header field ("ctrl", "facep2", "hitdefpersist",
	// "movehitpersist", "hitcountpersist") whose value did not parse as a
	// plain literal integer/bool — real MUGEN/Ikemen .cns files sometimes
	// give these fields a trigger expression instead (e.g.
	// "anim = IfElse(ceil(lifemax/2) < life ,181,182)",
	// "facep2 = 1-(prevstateno=[100,119])"). Keyed by lowercase field
	// name. A field with an entry here has its corresponding typed field
	// (Anim, PowerAdd, Juggle, SprPriority, Ctrl, FaceP2, HitDefPersist,
	// MoveHitPersist, HitCountPersist) left at its zero value; a field
	// without an entry here was a literal value and its typed field holds
	// it as usual. See
	// .vibe/decisions/023-statedef-numeric-header-fields-unevaluated-expression-escape-hatch.md
	// (numeric fields) and item 046 (boolean fields, extending the same
	// pattern).
	HeaderExprs map[string]string `json:"headerExprs"`
	// Controllers are the state controllers ([State N] blocks) that run
	// while this state is active, in file order.
	Controllers []Controller `json:"controllers"`
}

StateDef is a .cns [Statedef N] block: the state's header parameters plus the state controllers ([State N] blocks) that run while it is active.

func Parse

func Parse(r io.Reader) ([]StateDef, error)

Parse reads .cns text from r and returns the StateDefs ("[Statedef N]" blocks) it describes, in file order, each carrying its state controllers ("[State N]" blocks) in file order.

Trigger keys ("trigger1", "trigger2", ..., "triggerall", matched by a "trigger" prefix) are collected into a Controller's Triggers in file order rather than evaluated; the "type" key sets Controller.Type; every other key becomes a Parameters entry, normalized to lowercase for predictable lookup. A bracket section that is neither a valid "[Statedef N]" nor "[State N]" header is skipped without validating its content, matching def.Parse's tolerance for sections outside this package's scope — but a bracket line that looks like an attempted Statedef/State header yet fails to parse (a non-numeric or missing state number) returns a descriptive, line-numbered error rather than being silently skipped, as does a "[State N]" block with no enclosing Statedef and a reader that fails outright. A missing closing bracket on an otherwise-recognizable Statedef/State header attempt is recovered rather than treated as an error (see recoverMissingClosingBracket); a bracket line missing "]" that isn't recognizable as either header attempt is genuinely unrelated content (e.g. a decorative banner line) and is skipped without erroring, the same way a *closed* unrecognized section already is (see backlog item 053). See .vibe/decisions/012-cns-parse-header-detection-strategy.md. A content line inside a block that isn't a valid "key=value" pair (no "=" character) is ignored rather than erroring, the same way an unrecognized key already is (see backlog item 043). Comment lines (';', whole-line or trailing) are ignored. An empty input returns an empty, nil-error result.

type StateType

type StateType string

StateType is a .cns [Statedef N] block's "type" parameter: the state classification MUGEN/Ikemen uses to pick default behavior (e.g. gravity) when a controller doesn't explicitly override it.

const (
	// StateTypeStanding is a standing state ("S").
	StateTypeStanding StateType = "S"
	// StateTypeCrouching is a crouching state ("C").
	StateTypeCrouching StateType = "C"
	// StateTypeAir is an airborne state ("A").
	StateTypeAir StateType = "A"
	// StateTypeLiedown is a lying-down state ("L").
	StateTypeLiedown StateType = "L"
	// StateTypeUnchanged leaves the character's current state type
	// unchanged when entering this state ("U").
	StateTypeUnchanged StateType = "U"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL