This document is the long-form reference for writing, reading, and maintaining Vizzy programs in this repository.
It is written from the perspective of this project. The goal is practical:
- explain what each supported block family does
- show how it appears in XML and in C#-style authoring code
- document the safe authoring subset for
code -> XML - document pitfalls that can make Juno reject a generated program
- document fidelity-sensitive behavior that matters in real mission files
If you are new to this project, read these files in this order:
README.mddocs/VizzyAuthoringGuide.mddocs/RawPreservationGuide.mddocs/ExportValidationAndCoverageGuide.md- this file
This guide covers the block families currently supported by VizzyXmlConverter.cs, especially:
- instruction blocks
- expression blocks
- custom instructions
- custom expressions
- MFD widgets and widget interaction
- authoring patterns that round-trip reliably
It does not claim to be an exhaustive reverse-engineered reference for every block Juno has ever shipped. It is the supported reference for this repository.
That repository support now also includes:
- export-time structural validation
- repository-wide example verification
- clean-view imports plus metadata sidecars
When reading generated .vizzy.cs, you may see four different layers mixed together:
- high-level authoring syntax
- preserved metadata comments
- readable raw XML preservation
- manual layout hints
They are not the same thing.
This is the preferred layer for normal handwritten scripts.
VZTOPBLOCKVZBLOCKVZEL
These preserve imported graph identity.
Examples:
Vz.RawXmlConstant(...)Vz.RawXmlVariable(...)Vz.RawXmlCraftProperty(...)Vz.RawXmlEval(...)
These preserve exact XML fragments while remaining readable.
Example:
// VZPOS x=1200 y=-300These affect top-level layout in exported XML for authored blocks.
Vizzy programs in this project move between three forms:
- Vizzy XML
- C#-style Vizzy authoring code
- round-tripped Vizzy XML
There are two different success criteria.
For an existing Vizzy program exported from Juno:
XML -> code -> XML
must preserve the exact structure that Juno expects, ideally byte-for-byte.
For a new script written by hand:
code -> XML
must generate XML that Juno recognizes as a valid Vizzy program.
These goals overlap, but they are not the same.
This repository has a hard boundary that future maintainers and AI agents must understand.
When code came from real Vizzy XML and still retains metadata comments like:
VZTOPBLOCKVZBLOCKVZEL
the converter can often preserve the original XML block identity and structure exactly.
When a region is rewritten manually and those metadata comments no longer anchor the original nodes, the converter must build XML from authoring code patterns instead.
That is not the same task.
At that point, the block is no longer a strict round-trip block. It becomes a newly authored Vizzy graph.
Readable RawXml* does not automatically mean a block is fully reauthored. It may still mark an exact imported fidelity fragment inside an otherwise readable code region.
A large mission may contain both kinds of regions at the same time:
- preserved imported regions
- manually rewritten authoring regions
That mixed state is especially common in mission-scale files such as T.T.
When such a mission fails to load in Juno, the correct question is not just:
- "what differs from the original XML"
It is also:
- "which part of this mission is still preserved imported structure, and which part is now handwritten authoring code"
and sometimes:
- "which exact expressions must remain raw-preserved even inside an edited region"
Top-level custom blocks are especially sensitive in Juno.
A top-level custom instruction is represented as:
- one top-level
<Instructions>block - first child is a self-closing
<CustomInstruction ... /> - body instructions follow in the same top-level
<Instructions>block
The header must match Juno's conventions exactly, including fields such as:
callFormatformatnamestyleidpos
For authoring-created custom instructions, spacing inside callFormat and format matters. Use the same shape emitted by working Juno XML.
Top-level custom expressions live under <Expressions>.
For authoring-created custom expressions, ensure:
- they are direct children of
<Expressions> - they carry a valid
pos - their
callFormat,format,name, andstylematch Juno's established pattern
Missing pos on a new top-level custom expression is a real structural failure, not just a cosmetic difference.
Top-level layout can now be guided manually with the compact form actually supported by VizzyCode:
// VZPOS x=1200 y=-300A Vizzy program normally has:
- one
<Program>root - one
<Variables>section - one or more
<Instructions>blocks - one
<Expressions>section
Top-level Instructions blocks usually start with one of:
- an
Event - a
CustomInstruction - or a preamble block for free-standing instructions
For authoring new scripts, the simplest safe entry point is:
Vz.Init("Program Name");
using (new OnStart())
{
// instructions...
}
Console.WriteLine(Vz.context.currentProgram.Serialize().ToString());For fidelity work on existing XML, also use the round-trip harness:
dotnet run --project TestRT -c Release -- "<input.xml>" "<output.xml>" "<code.txt>"For broader repository export coverage, also use:
dotnet run --project VizzyCode.csproj -c Release -- --verify-vizzyWhen a mission export does not appear in the Vizzy editor:
- compare the exported XML against working non-exported examples in
Vizzy examples - identify whether the failing region corresponds to preserved imported blocks or handwritten code
- compare repo CLI export and VS Code plugin export to rule out tool drift
- inspect top-level custom blocks first
- inspect control-flow structural encoding second
- inspect raw-preserved fragments third
- inspect the export validator result before inventing a new theory
Why this order:
- top-level block shape can make Juno reject the whole program immediately
- control-flow shape errors can make individual regions invalid
- formatting differences in nested expressions usually matter after the top-level graph is already valid
The validator currently catches some of these problems automatically, but not every future bad XML shape will already be encoded there. That is why comparison against working examples still matters.
When raw-preserved fragments are present, prefer understanding them first instead of rewriting them from appearance alone.
T.T is a reference case for why mission-scale files need stricter handling.
The current T.T work showed a real mixed-state scenario:
- some regions still preserved imported Vizzy metadata
- other regions had been manually rewritten into new authoring code
- new top-level custom instruction and custom expression blocks were introduced
That means a failure in T.T is not automatically "the exporter broke a round-trip".
It may be:
- a broken exporter for a handwritten authoring pattern
- a structurally invalid handwritten block
- or a mix of both
The easiest way to understand Vizzy is to group blocks by role:
- flow blocks control execution order
- action blocks change the craft, UI, or program state
- expression blocks compute values
- custom blocks package reusable logic
The sections below follow that structure.
Event blocks start execution threads. They are the roots of most top-level Instructions columns in Vizzy.
Purpose:
- run code when the program starts in flight
Typical XML:
<Event event="FlightStart" style="flight-start" />Typical code:
using (new OnStart())
{
// body
}This converter also supports several other event forms in existing XML:
- flight end
- receive message
- docked
- change SOI
- part collision
- part explode
Purpose:
- store human-readable notes in the Vizzy graph
Typical XML:
<Comment style="comment">
<Constant style="comment-text" canReplace="false" text="My note" />
</Comment>Typical authoring code:
// This is a notePurpose:
- assign a value to a variable
Typical XML:
<SetVariable style="set-variable">
<Variable list="false" local="false" variableName="Pitch" />
<Constant number="45" />
</SetVariable>Typical authoring code:
Pitch = 45;Purpose:
- increment or add to a variable
Typical authoring code:
counter += 1;Variable references appear inside expressions.
There are several important distinctions:
- global variables
- local variables
- list variables
For fidelity-sensitive cases, this converter can preserve raw variable metadata exactly.
Purpose:
- repeat while a condition is true
Typical code:
using (new While(condition))
{
// repeated instructions
}Purpose:
- conditional branching
Typical code:
using (new If(conditionA))
{
}
using (new ElseIf(conditionB))
{
}
using (new Else())
{
}Purpose:
- run a loop with an iterator variable and explicit start, end, and step
Important note:
For blocks can carry local-variable metadata that matters for fidelity. This project preserves that carefully for XML round-trips.
Purpose:
- yield execution for a time interval
Typical code:
Vz.WaitSeconds(0.1);Purpose:
- pause until a condition becomes true
Typical code:
using (new WaitUntil(condition)) { }This is one of the safest blocks for handwritten scripts.
Purpose:
- show a user-facing message on screen for a duration
Typical code:
Vz.Display("Launching", 7);Fidelity note:
- real XML often uses
DisplayMessage - code form may still appear as
Vz.Display(...) - round-trip work must preserve the original XML instruction identity
Purpose:
- write a simple log line
Typical code:
Vz.Log("Entering coast phase");Fidelity note:
- real XML may use
LogMessage - code form may still appear as
Vz.Log(...) - the converter now preserves
LogMessagecorrectly during round-trip
Purpose:
- trigger staging
Typical code:
Vz.ActivateStage();Purpose:
- write a raw craft input axis or toggle
Typical code:
Vz.SetInput(CraftInput.Throttle, 1);Purpose:
- command a heading-related autopilot property
Typical code:
Vz.SetTargetHeading(TargetHeadingProperty.Pitch, 45);
Vz.SetTargetHeading(TargetHeadingProperty.Heading, 90);This repository supports the manual authoring alias:
Vz.LockHeading(90, 45);During code -> XML, it is expanded to heading and pitch target instructions.
Purpose:
- lock orientation to a nav indicator such as
CurrentorPrograde
Typical code:
Vz.LockNavSphere(LockNavSphereIndicatorType.Current, 0);
Vz.LockNavSphere(LockNavSphereIndicatorType.Prograde, 0);Purpose:
- toggle action groups
Purpose:
- change a part-level property
This family is important for craft automation and can be structurally sensitive.
Purpose:
- ask the player for a value
This block has fidelity-sensitive attribute ordering in some real examples, so the converter preserves it carefully.
Purpose:
- send data or messages locally or across craft scopes depending on flags
Purpose:
- start a handler thread when a message arrives
Lists are useful for sequences, lookup tables, and lightweight storage.
Supported list instruction families include:
- add
- insert
- remove
- set
- clear
- sort
- reverse
Supported read-oriented list expressions include:
- get item
- list length
- index lookup
- inline list creation
Purpose:
- package reusable instruction sequences
Examples:
- gear deployment
- solar panel deployment
- repeated mission actions
Purpose:
- package reusable value computations
Use them for:
- orbital calculations
- vector helpers
- utility math
- string formatting helpers
Important fidelity note:
Custom expression headers may include formatting metadata and canvas position metadata that matter for exact reproduction.
Represents:
- number
- text
- bool
Important fidelity note:
- some string constants intentionally contain leading or trailing spaces
- those spaces must be preserved exactly
Typical operations:
+-*/%^- min
- max
- random
- atan2
Typical operations:
andor
Typical operations:
- equals
- not equals
- greater than
- less than
- greater-or-equal
- less-or-equal
Purpose:
- ternary-style conditional value
Typical functions include:
- abs
- sqrt
- floor
- ceiling
- round
- sin
- cos
- tan
- asin
- acos
- atan
- ln
- log
- deg2rad
- rad2deg
Typical vector operations include:
- x, y, z component
- length
- normalization
- dot
- cross
- angle
- projection
Purpose:
- read game state from the craft or related systems
Typical groups:
- altitude
- orbit
- velocity
- performance
- input
- time
- target
- navigation
Purpose:
- read planet-scoped values or transform between frames
Use it for:
- radius
- mass
- parent body
- atmosphere height
- solar position
- orbital data
Important fidelity notes:
- nested
Planetexpressions inside arithmetic are common in real mission code - examples such as
Vz.Planet(...).Mass() / Vz.Planet(...).Mass()must stay as nestedPlanetchildren insideBinaryOp - if such expressions degrade into
Variable.variableName="Vz.Planet(...)", the XML is broken
Purpose:
- evaluate a raw expression string that does not fit a higher-level Vizzy block cleanly
Purpose:
- call a named custom expression
Typical operations:
- format
- join
- contains
- length
- substring
- friendly time or distance formatting
Use them for:
- display text
- user input prompts
- structured logs
- formatted countdowns
- formatted distances
Important fidelity notes:
friendlyis not just a generic string helper- real XML often requires
subOp="time"orsubOp="distance" - these nodes are used in encounter countdowns, landing readouts, and burn timing output
- trailing or leading spaces in adjacent
Constant text="..."nodes must be preserved exactly
Common real examples:
Vz.StringOp("friendly", Time, "")
Vz.StringOp("friendly", Vz.Length(vector), "")
Vz.Join("Launch Time: ", Vz.StringOp("friendly", Time, ""), "")Purpose:
- read terrain-derived information such as terrain height or color
Purpose:
- inspect part-specific data
- convert between part-local and PCI-like frames
Purpose:
- probe geometry or environment intersections
Supported families include:
- lat/long/AGL to position
- position to lat/long/AGL
Supported MFD expression families include:
- widget property reads
- widget event message reads
- pixel reads
- hex color conversion
This project supports a substantial MFD and widget block family.
Supported concepts include:
- widget creation
- widget mutation
- widget layering
- texture initialization
- pixel and line updates
- map, navball, gauge, label, and sprite related flows
If you are writing a fresh script by hand, prefer this style:
- one
OnStartevent LockNavSpherefor orientation changesSetInputfor throttle changesWaitUntilfor milestonesSetTargetHeading(TargetHeadingProperty.Pitch, value)for pitch programsDisplayfor visible phase changes
Use extra caution with:
- handwritten aliases not already emitted by the converter
- raw expression helper shortcuts
- unusual widget code patterns
- part or craft property blocks with style-sensitive metadata
- custom expressions when exact header fidelity matters
- strings where spaces are semantically part of the original XML
friendlystring formatting- message or log blocks whose XML names differ from their code aliases
- large real mission programs such as
T.T. Mission Program
Before calling a new authoring template safe, check all of the following:
- The generated XML opens in Juno.
- The generated XML does not contain
[TODO]comments. - The program has a valid
Program -> Variables -> Instructions -> Expressionsstructure. - All key control blocks mapped to actual Vizzy XML, not fallback comments.
- If the example came from an existing Vizzy XML file, it round-trips cleanly.
- Strings with surrounding spaces are still identical.
friendlystring nodes still have the correctsubOp.LogMessageandDisplayMessagenodes did not collapse into a different XML instruction family.- Nested
Planetexpressions still appear as real expression nodes, not variable-name fallbacks. - Export validation reports zero errors.
The current validator already enforces some block-family rules directly.
Every <EvaluateExpression> must include:
style="evaluate-expression"
This matters for:
Vz.EVz.PiVz.InfinityVz.NaNVz.Eval(...)- helper-generated evaluate-expression fragments inside higher-level syntax
VizzyCode must export the Juno-safe else shape:
ElseIf style="else"- leading
<Constant bool="true" /> - then
<Instructions>
Raw <Else> nodes are treated as invalid.
These top-level roots must include pos:
EventCustomInstructionCustomExpressionComment
Top-level CustomInstruction and CustomExpression also need:
callFormatformatname
Top-level Event also needs:
event
These are not style preferences. Missing metadata here has already produced real editor-loading failures.
When in doubt:
- derive new examples from real working Vizzy XML
- preserve documentation in English
- prefer canonical emitted syntax
- verify support in
VizzyXmlConverter.csbefore documenting a pattern as safe
If a real mission file exposes a fidelity bug, document the exact pattern here after fixing it.
When a new Juno rejection is diagnosed, also decide whether it should become:
- a new parser/export fix
- a new export validator rule
- a new example in this guide
That is how this document should keep growing.