1. The Problem: Temporal Evaluation in Divination
In any divinatory system worth formalizing, you eventually hit the same core question: is this thing arriving or departing? Is the outcome the querent seeks actively materializing, or is it already dissipating past the event horizon?
Western geomancy handles this loosely. Latin texts use adjectives — "mobile," "fixed," "common" — and leave the practitioner to synthesize direction from context. The Indian Ramal tradition, by contrast, prescribes a strict, enumerated state taxonomy that maps every one of the sixteen figures to an explicit directional-kinetic profile. In software engineering terms, it defines a finite state machine with deterministic transition rules.
The SAGE platform encodes this state machine directly in its computation engine. Every figure placed on the board is not merely a symbol — it is a struct carrying a gati field whose value is drawn from a closed, canonical enum. The algorithm reads this field and branches accordingly. There is no ambiguity, no fuzzy inference, no probabilistic guessing.
The Ramal Gati system is not a metaphor for a state machine. It is a state machine — a classification system that assigns each of 16 input symbols to one of 4 discrete states, where each state has deterministic behavioral implications for timing, durability, and spatial directionality.
2. The Four States: Dakhil, Kharij, Sabit, Munqaleb
Every geomantic figure in the Ramal tradition carries a Gati (गति) — a Sanskrit-derived term meaning "movement" or "trajectory." The Gati profile is not computed at runtime; it is an intrinsic, immutable property of the figure itself, defined at the schema level. The SAGE engine stores this in a master metadata map called RAMAL_FIGURE_MAP.
The four canonical Gati states are:
Dakhil (داخل / Inward / Entering)
Centripetal energy. The figure draws resources, people, and outcomes toward the querent. In state machine terms, this is the accumulation state — the system is absorbing inputs. If the Seal (final synthetic figure) of a chart carries a Dakhil Gati, the SAGE engine interprets the queried event as actively approaching manifestation.
Kharij (خارج / Outward / Exiting)
Centrifugal energy. The figure pushes influence, assets, and outcomes away from the querent. This is the dispersal state. A Kharij Seal signals that the queried outcome is already past its peak, dissipating outward, or requires the querent to project effort externally to achieve results.
Sabit (ثابت / Static / Fixed)
Contained energy. The figure neither absorbs nor releases — it circulates within its own boundaries. This is the hold state. A Sabit Seal indicates long-term consolidation: the outcome will materialize, but on a timeline measured in months rather than days.
Munqaleb (منقلب / Volatile / Transforming)
Oscillating energy. The figure rapidly alternates between inward and outward phases, making the outcome inherently unstable. This is the transient state — the most dangerous for long-term predictions. A Munqaleb Seal triggers a discord penalty in the SAGE coherence algorithm because it signals that the final result is subject to sudden reversal.
3. The Complete State Assignment Table
The following table is the canonical SAGE registry. Each of the sixteen figures is statically assigned to exactly one Gati state. This assignment is immutable — it cannot be overridden at runtime. The engine looks up the figure's pattern key in RAMAL_FIGURE_MAP and reads the "gati" field directly.
| Figure ID | Pattern | Gati State | Tatva | Graha | Nature |
|---|---|---|---|---|---|
| Tariq | (1,1,1,1) | Munqaleb | Water | Moon | Mobile |
| Jamaat | (2,2,2,2) | Sabit | Water | Moon | Fixed |
| Utba-el-Dakhil | (1,2,1,1) | Dakhil | Water | Venus | Fixed |
| Utba-el-Kharij | (1,1,2,1) | Kharij | Fire | Mars | Mobile |
| Nusrat-el-Dakhil | (2,2,1,1) | Dakhil | Water | Sun | Fixed |
| Nusrat-el-Kharij | (1,1,2,2) | Kharij | Air | Sun | Mobile |
| Qabz-el-Dakhil | (2,1,2,1) | Dakhil | Earth | Jupiter | Fixed |
| Qabz-el-Kharij | (1,2,1,2) | Kharij | Fire | Venus | Mobile |
| Bayaz | (2,2,1,2) | Sabit | Air | Mercury | Fixed |
| Humrah | (2,1,2,2) | Kharij | Fire | Mars | Mobile |
| Ijtima | (2,1,1,2) | Sabit | Air | Mercury | Fixed |
| Uqala | (1,2,2,1) | Sabit | Earth | Saturn | Fixed |
| Ankis | (2,2,2,1) | Kharij | Earth | Saturn | Mobile |
| Sahu | (1,2,2,2) | Dakhil | Air | Jupiter | Mobile |
| Khad | (2,1,1,1) | Dakhil | Earth | Rahu | Mobile |
| Munkis | (1,1,1,2) | Kharij | Fire | Ketu | Mobile |
Dakhil: 5 figures (31.25%) — Kharij: 6 figures (37.5%) — Sabit: 4 figures (25%) — Munqaleb: 1 figure (6.25%). The system is biased toward dispersal: the universe, per the Ramal Siddhanta, tends toward entropy. Only Tariq occupies the volatile Munqaleb state — it is a singularity in the state space.
4. The Three-Axis Engine: Gati × Swabhava × Temperament
The Gati state alone does not fully determine a figure's operational behavior. The SAGE doctrine engine evaluates each figure across three orthogonal axes, producing a composite Doctrinal Vector:
- Temperament (Ethical Axis): Is the figure Benefic (Saumya), Neutral, or Malefic (Krura)?
- Gati (Spatial Axis): Is the energy flowing Inward (Dakhil), Static (Sabit), or Outward (Kharij)?
- Swabhava (Temporal Axis): Is the kinetic behavior Fixed (Sthira), Dual (Dwisvabhava), or Mobile (Chara)?
The Doctrinal Vector is expressed as a triple: [Temperament, Direction, Nature]. For example, Utba-el-Dakhil carries the vector [Benefic, Inward, Fixed] — meaning it is a nourishing force that draws resources inward and holds them permanently. Compare this to Nusrat-el-Kharij at [Benefic, Outward, Mobile] — equally nourishing, but its energy is fleeting and projected outward. Same temperament, radically different operational reality.
The Benefic Cross-Tabulation Matrix
This matrix reveals the structural skew: benefic forces cluster heavily in the Inward-Fixed quadrant, forming a dense core of sustainable accumulation.
| Swabhava \ Gati | Inward (Dakhil) | Static (Sabit) | Outward (Kharij) |
|---|---|---|---|
| Fixed (Sthira) | Utba-el-Dakhil, Nusrat-el-Dakhil, Qabz-el-Dakhil | Jamaat | — |
| Dual (Dwisvabhava) | Khad | Ijtima | — |
| Mobile (Chara) | Sahu | Bayaz | Nusrat-el-Kharij |
The Malefic Cross-Tabulation Matrix
By contrast, malefic forces concentrate along the Outward-Mobile row — an aggressive, volatile corridor with no inward pathway. The system has no malefic figure that enters the querent's sphere. Danger, per the Ramal doctrine, always leaves or locks down.
| Swabhava \ Gati | Inward (Dakhil) | Static (Sabit) | Outward (Kharij) |
|---|---|---|---|
| Fixed (Sthira) | — | Uqala | Ankis |
| Dual (Dwisvabhava) | — | — | Munkis |
| Mobile (Chara) | — | — | Utba-el-Kharij, Humrah, Qabz-el-Kharij |
Notice the structural impossibility: no malefic figure is ever Dakhil. In the Ramal Siddhanta, destructive forces cannot "enter" your life through the oracle's own state mechanics — they can only sit upon you (Sabit) or drain away from you (Kharij). This is not a coincidence. It is a deliberate doctrinal constraint baked into the 16-figure state space, and the SAGE engine enforces it as an invariant.
5. The SAGE Implementation: From Doctrine to Code
The transition from doctrinal taxonomy to production code is remarkably direct. Because the Gati system is a static classification (no runtime computation), the SAGE engine implements it as a dictionary lookup with branching conditionals.
5.1 The Metadata Registry
The canonical state assignments live in ramal_engine.py as a flat dictionary keyed by binary pattern strings. Each entry carries the figure's Gati as an immutable field:
# From engine/ramal/ramal_engine.py
RAMAL_FIGURE_MAP = {
"1211": {
"id": "Utba-el-Dakhil",
"tatva": "Water",
"graha": "Venus",
"gati": "Dakhil", # ← The state assignment
"nature": "Fixed",
"pattern": (1, 2, 1, 1)
},
"1121": {
"id": "Utba-el-Kharij",
"tatva": "Fire",
"graha": "Mars",
"gati": "Kharij", # ← Outward state
"nature": "Mobile",
"pattern": (1, 1, 2, 1)
},
# ... 14 more entries
}This is pure data, not logic. The Gati value is read, never computed. There is no function that inspects the binary pattern and derives the Gati — it is an assigned, immutable property defined by tradition.
5.2 The Timing Conditional
The practical consequence of the Gati state machine materializes in the SAGE timing engine. After the chart is computed and the Seal (final synthetic figure) is identified, a simple if/elif chain reads the Seal's Gati and emits a timing estimate:
# From logic/premium_ramal.py — Timing Estimate Engine
if seal["gati"] == "Dakhil":
timing_range = "1-7 days"
rationale = "Swift inward manifestation"
elif seal["gati"] == "Kharij":
timing_range = "2-4 weeks"
rationale = "Gradual outward unfolding"
elif seal["gati"] == "Munqaleb":
timing_range = "7-21 days"
rationale = "Volatile/transforming manifestation"
else: # Sabit
timing_range = "2-6 months"
rationale = "Long-term consolidation"If you squint at this, you will recognize a textbook state machine implementation: a single input variable (seal["gati"]) dispatches deterministically to one of four branches. No probability distributions, no weighted sampling — just a canonical switch on an enumerated type.
5.3 The Discord Penalty
Beyond timing, the Gati state feeds directly into the SAGE coherence scoring algorithm. A Munqaleb Seal is treated as a structural red flag:
# Volatile Seal detection — adds discord to coherence score
if seal.get('gati') == "Munqaleb":
discord += 10
reasons.append(
"Volatile Seal: Final outcome is unstable "
"or subject to sudden change"
)This is not an aesthetic judgement — it is a deterministic penalty applied because the Munqaleb state, by definition, signals oscillation. The coherence engine deducts 10 points from the chart's overall reliability score, ensuring that readings with volatile endpoints are flagged with appropriate uncertainty.
5.4 The Gati Distribution Counter
For aggregate chart analysis, the SAGE engine counts the Gati distribution across all 16 house figures and emits the profile as structured data:
# Aggregate Gati profile across all figures in chart
"gati": {
"dakhil": sum(1 for f in figures if f['gati'] == 'Dakhil'),
"kharij": sum(1 for f in figures if f['gati'] == 'Kharij'),
"sabit": sum(1 for f in figures if f['gati'] == 'Sabit'),
"munqaleb": sum(1 for f in figures if f['gati'] == 'Munqaleb')
}This distribution vector is consumed by the AI narrative generation layer, the Purushartha (four life-goals) posture calculator, and the chart reconciliation engine. A chart dominated by Dakhil figures carries a fundamentally different narrative arc than one dominated by Kharij — even if the temperament profile is identical.
6. Formal Properties of the Ramal State Machine
For the state machine enthusiast, the Ramal Gati system exhibits several notable formal properties:
- Determinism: Every input (figure pattern) maps to exactly one state. There is no non-deterministic branching.
|states| = 4,|inputs| = 16, and the transition function is a total function. - Immutability: States are assigned at schema definition time, not computed from runtime inputs. The transition table is a static lookup, making the system trivially verifiable.
- Asymmetric distribution: The state space is not uniformly partitioned. Kharij dominates (6/16), while Munqaleb is a singleton. This distribution itself encodes doctrinal semantics — a philosophical statement about the nature of temporal flow.
- Composability: The Gati state composes with the orthogonal Temperament and Swabhava axes to produce a 3-dimensional doctrinal vector, enabling rich type-theoretic reasoning without increasing the state machine's branching complexity.
- Invariants: The system enforces structural invariants — e.g., no Malefic figure may carry a Dakhil state. These invariants are not checked at runtime; they are guaranteed by the static definition.
If you model this in a typed language, the Gati system is essentially a discriminated union (or sum type): type Gati = Dakhil | Kharij | Sabit | Munqaleb. Each variant carries distinct behavioral semantics that the consuming functions pattern-match against exhaustively. The ancient practitioners did not have type theory — but they arrived at the same structural solution through centuries of empirical refinement.
7. Representing the Full Machine
If you wanted to model the entire Ramal Gati state machine as a standalone, self-contained module — stripped of all other SAGE infrastructure — it would look like this:
# ramal_gati_machine.py — Minimal, self-contained state machine
from enum import Enum
from typing import Dict, Tuple, NamedTuple
class Gati(Enum):
DAKHIL = "Dakhil" # Inward / Entering
KHARIJ = "Kharij" # Outward / Exiting
SABIT = "Sabit" # Static / Contained
MUNQALEB = "Munqaleb" # Volatile / Oscillating
class TimingResult(NamedTuple):
range: str
rationale: str
# The static transition table (pattern → state)
STATE_TABLE: Dict[str, Gati] = {
"1111": Gati.MUNQALEB, # Tariq
"2222": Gati.SABIT, # Jamaat
"1211": Gati.DAKHIL, # Utba-el-Dakhil
"1121": Gati.KHARIJ, # Utba-el-Kharij
"2211": Gati.DAKHIL, # Nusrat-el-Dakhil
"1122": Gati.KHARIJ, # Nusrat-el-Kharij
"2121": Gati.DAKHIL, # Qabz-el-Dakhil
"1212": Gati.KHARIJ, # Qabz-el-Kharij
"2212": Gati.SABIT, # Bayaz
"2122": Gati.KHARIJ, # Humrah
"2112": Gati.SABIT, # Ijtima
"1221": Gati.SABIT, # Uqala
"2221": Gati.KHARIJ, # Ankis
"1222": Gati.DAKHIL, # Sahu
"2111": Gati.DAKHIL, # Khad
"1112": Gati.KHARIJ, # Munkis
}
TIMING_MAP: Dict[Gati, TimingResult] = {
Gati.DAKHIL: TimingResult("1-7 days", "Swift inward manifestation"),
Gati.KHARIJ: TimingResult("2-4 weeks", "Gradual outward unfolding"),
Gati.MUNQALEB: TimingResult("7-21 days", "Volatile/transforming"),
Gati.SABIT: TimingResult("2-6 months", "Long-term consolidation"),
}
def evaluate_seal(pattern: str) -> TimingResult:
"""Given a 4-digit pattern string, return the timing verdict."""
state = STATE_TABLE[pattern]
return TIMING_MAP[state]Sixteen entries. Four states. One lookup. No branching logic beyond the initial dispatch. This is the entire Ramal temporal evaluation engine, distilled to its formal essence.
8. Why This Matters
For those of us who build production systems, the Ramal Gati architecture offers a surprisingly relevant case study in domain-driven design. The original practitioners — working without compilers, type systems, or formal verification tools — arrived at a classification system that is:
- Exhaustive: Every possible input is accounted for. There are no unhandled cases.
- Deterministic: The same input always produces the same output.
- Composable: The Gati state composes cleanly with independent axes (Temperament, Swabhava) without combinatorial explosion.
- Self-documenting: The Arabic state names — Dakhil (entering), Kharij (exiting), Sabit (stable), Munqaleb (flipping) — are themselves a specification language.
- Invariant-preserving: Structural constraints (no Malefic-Dakhil combinations) are guaranteed by the data, not enforced by runtime assertions.
In an era where we routinely over-engineer configuration systems with YAML hierarchies, feature flags, and machine learning classifiers, there is something clarifying about a 1,000-year-old state machine that does exactly what it needs to do, using exactly four symbols, in exactly one lookup.
The dots knew what they were doing.
Experience the State Machine in Action
Every SAGE reading evaluates the Gati state machine in real time. Try it for free and see the temporal engine resolve your query deterministically.
✧ Get 11 Free Energy Tokens ✧