Mug Typography scripting is a feature for controlling text layout and Character/Part animations with Lua 5.4 scripts written in the plugin's Script Text. For development, you can use the VS Code extension "Mug Typography Scripting Simulator" (MT Sim) and the installation-free online simulator available in a browser. Both let you preview and debug scripts without launching the host application.
1. Basic Model
A script can define up to three callback functions. All are optional, and callbacks that are not defined are simply skipped.
Frame evaluation path (every frame)
Every frame
└─ OnPreLayout(ctx)
Modify base values for layout, Global settings, and camera; declare shapes and custom glyphs
↓
Generate Character and Part layout
↓
OnLayout(ctx)
Individually adjust the generated Characters, Parts, and Shapes
↓
OnPath(ctx)
Adjust the outlines of selected Parts and Shapes
↓
RenderingThe important point is that the execution timing and the objects that can be manipulated differ for each callback. Define only the callbacks for the stages appropriate to your purpose.
OnPreLayoutruns before layout generation on every frame. It changes the generation conditions for Characters and Parts, such as text, font, tracking, Global settings, and camera, and declares shapes or custom glyphsOnLayoutruns after Characters and Parts have been generated. It adjusts the position, rotation, color, and other properties of each generated element and shapeOnPathruns after individual adjustments and before rendering. It replaces or rebuilds the outlines of selected Parts and Shapes
Later callbacks can read values finalized in earlier stages. However, they cannot go back and rewrite values in a stage that has already been processed. For example, OnLayout can read ctx.global and ctx.camera, but cannot modify them.
Changes Are Valid Only Within the Frame
Every frame, ctx is passed to callbacks as state recreated from the host's Inspector values. Values written by callbacks are used only to render that frame, and the next frame starts again from the Inspector values. They are never written back to the Inspector or keyframes.
For example, ctx.global.rotation = ctx.global.rotation + 1.0 does not mean "continue rotating by one degree every frame"; it means "always use the Inspector value + 1 degree." To advance the rotation while preserving the Inspector setting, add a frame-independent delta derived from ctx.time, as in ctx.global.rotation = ctx.global.rotation + ctx.time * 20.0. Direct assignment such as ctx.global.rotation = ctx.time * 20.0 should be used only when intentionally replacing the Inspector rotation completely.
2. Frame Independence Requirement (Stateless Execution)
The host does not necessarily evaluate frames in chronological order. The result must be the same during scrubbing, reverse playback, caching, and parallel rendering. The result of each frame must be determined only by the current input values and must not depend on the previous frame's result or the order in which frames are evaluated. In this document, this property is called frame independence.
- If you modify a global variable or a
localvariable declared outside a function, do so only to pass a value between callbacks within the same frame, and overwrite it from the current input values on every frame. Do not carry forward or accumulate values from the previous frame - Do not retain references such as
ctxorctx.chars[index]after the callback ends - Do not assume fixed values for the character count, Part count, or the shape of a particular font
- Derive the value for the frame directly from
ctx.timeinstead of using a sequential simulation - Always use
ctx.char_count/ctx.part_countwhen iterating over Characters or Parts
Script Metadata
Writing @<name> <value> as a Lua comment at the beginning of a script file lets you declare the required API, MT Sim behavior, initial settings, and Script Inputs. Metadata is not Lua API executed inside callbacks, but the host plugin also interprets @api_level, @input, and @time_independent.
Script Recognition and Execution Settings
| Declaration | Description |
|---|---|
@MugTypography | When placed in the leading uninterrupted -- single-line comment block starting at line 1 (or within the first 5 lines in MT Sim), the file is recognized as a Mug Typography script. It is required for code completion, simulator integration, and host @input parsing, and is always included on line 1 of distributed samples. @mt or @mug can also be used |
@duration <seconds> | Sets the simulator's initial playback duration. Example: @duration 5.0 |
@required 3d | Enables the 3D projection setting (Enable 3D projection) when the simulator starts. 3dprojection can also be used |
Recommended Initial Settings
Use @recommend <setting> only when a script's presentation requires specific initial conditions. Keep specifications to the minimum necessary. Specifying text, position, background, and other values indiscriminately makes it difficult to tell whether the displayed result comes from the script or from the Inspector's initial settings. Do not use it for scripts that work with arbitrary settings.
| Declaration | Description |
|---|---|
@recommend bg <specification> | Initial background value. Specify a solid color with #RRGGBB / #RRGGBBAA, a checkerboard background with checkerboard / checker, or solid to turn the checkerboard off explicitly. A color and a mode can be written on the same line. Example: @recommend bg checkerboard #ffffff |
@recommend fill <color> | Initial Global fill color. Specify it as #RRGGBB / #RRGGBBAA. Example: @recommend fill #d8d4f2 |
@recommend gradient <color> | Initial Gradient End Color. A script can use it as a secondary color even when the gradient mode is none. Example: @recommend gradient #ff4f9a |
@recommend pos <X> <Y> | Initial Global text position. Larger X values move right, and larger Y values move up. Examples: @recommend pos 0.5 0.5 for the center, or @recommend pos 0.5 0.18 for the lower part of the screen |
@recommend text "<string>" | Initial Text value. Enclose it in "..." or '...'. \n becomes a line break, while \" \' \\ become the respective literal characters. Example: @recommend text 'swaying\ntext'. "" starts with empty text, for full-screen backgrounds that draw without body text |
@recommend vertical <boolean> | Initial writing direction: true for vertical, false for horizontal. Applied on script load; later Inspector changes are respected |
@recommend lang <language code> | Automatically selects a font that supports the language. Example: @recommend lang ja. It can be omitted when the language can be inferred from text, and an explicit value takes precedence over automatic detection |
These recommended initial settings are also applied when the simulator's Reset button is pressed. The VS Code extension and online simulator show a color swatch for the bg, fill, and gradient hex values. Select the swatch to change the value with the editor's standard color picker.
Script Inputs
Script Inputs are available in API Level 8 and later. @input declares the slot, Inspector label, and optional initial value shown under Script Inputs. Its syntax is:
@input <type> <index> "<label>" [default=<value>]-- @input number 1 "Amplitude" default=15.0
-- @input number 2 "Speed" default=1.0
-- @input color 1 "Primary Color" default={1.0, 0.4, 0.2, 1.0}
-- @input text 1 "Caption" default="Hello"
-- @input boolean 1 "Enable Glow" default=true| type | index | Lua access | When default is omitted |
|---|---|---|---|
number | 1-14 | ctx.inputs.numbers[index] | 0.0 |
color | 1-8 | ctx.inputs.colors[index] | {1.0, 1.0, 1.0, 1.0} |
text | 1-4 | ctx.inputs.texts[index] | Empty string |
boolean | 1-8 | ctx.inputs.booleans[index] | false |
boolean requires API Level 10 or later. Its default accepts only true or false.
- Types are ASCII case-insensitive. Slots are displayed in Number, Color, Text, Boolean index order, not declaration order
- Labels must contain 1-64 UTF-8 bytes and support only
\"and\\escapes - A Number default must be finite and within
-1000000.0-1000000.0; a Color default has exactly four RGBA channels within0.0-1.0; a Text default is a single-line quoted string without NUL and no longer than 4096 UTF-8 bytes; a Boolean default istrueorfalse - Duplicate type/index pairs, unknown types, out-of-range indexes, malformed quotes, escapes or defaults, and trailing tokens are metadata errors. An error preserves the last valid Inspector presentation, reports
Input line <N>: <reason>inScript Status, and does not prevent Lua compilation or execution - Only declared slots appear in the Inspector. With no declarations, the complete
Script Inputsgroup is hidden. Lua always reads the fixed-length arrays, including undeclared slots - When Script Text changes, an initial value is applied only to a newly declared slot or one whose effective default changed. A label-only change preserves its value and keyframes
- A default does not overwrite a slot with keyframes. Project loading preserves stored values and keyframes without reapplying metadata defaults
Time Independence
-- @time_independent takes no value. It allows the host to reuse rendered overlays across frames with unchanged Inspector values and excludes this script from frame-varying notification.
Place -- @time_independent in the leading uninterrupted -- comment block, before any blank or indented line, code, or block comment. Omission retains time-dependent behavior. This is an author contract, with no automatic analysis: do not declare it when output depends on ctx.time, ctx.frame, timeline information, call counts, or external state. Changes to Inspector values (including animated @input slots), script source, or render dimensions still invalidate reuse. Other time-varying effects such as Pulse still require rendering and notification. The simulator parses the declaration but continues evaluating preview frames.
Sample Browser Display Information
This information is displayed in the web simulator's sample browser. When omitted, the file name is used as the title, and the author, version, and API level are not displayed.
| Declaration | Description |
|---|---|
@title <display name> | Title displayed on the card. Example: @title Callback Flow Basics |
@author <author name> | Author name. Example: @author Mug |
@version <version> | Script version. Example: @version 1.0 |
@api_level <number> | API level required by the script. Example: @api_level 6. A plugin that does not support the declared level refuses to load the script |
@animation_id | ID of a bundled sample (its genre's three capital letters plus a three-digit number), e.g. FAD002, displayed and searchable in the browser. Your own scripts do not need it |
@description | Multi-line description inside a block comment. The line break immediately after @description is ignored |
@description.<locale> | Localized sample description, e.g. @description.ja or @description.en. The browser falls back to @description when a translation is missing and searches all translations |
Example
-- @MugTypography
-- @duration 5
-- @title Callback Flow Basics
-- @author Mug
-- @version 1.0
-- @api_level 8
-- @input number 1 "Amplitude" default=15.0
--[[ @description
Reference: callback execution timing and the frame-independent
scripting contract.
]]Placement and Parsing Range
@MugTypography and @duration / @required / @recommend / @input / @title / @author / @version / @time_independent / @api_level must be placed within the run of consecutive -- single-line comments starting at line 1. The parsing range ends when an @description block comment or a line that is not a comment (including a blank or indented line) is reached. Therefore, write single-line metadata before @description. Multiple @recommend lines and multiple settings on one line are both supported. In generated code, put @MugTypography on line 1 and the other metadata and description immediately after it.
3. Callbacks
| Callback | Invocation timing | Main purpose |
|---|---|---|
OnPreLayout(ctx) | Before layout generation (every frame) | Change the generation conditions for Characters and Parts and the Global settings; declare shapes and custom glyphs |
OnLayout(ctx) | After layout generation (every frame) | Individually adjust generated Characters, Parts, and Shapes |
OnPath(ctx) | After OnLayout (every frame) | Replace or rebuild the outlines of selected Parts and Shapes |
For fields readable and writable in OnPreLayout / OnLayout, see the ctx reference. Fields for OnPath are described in §3.3.
3.1 OnPreLayout(ctx)
| Item | Description |
|---|---|
| Signature | function OnPreLayout(ctx) |
| Argument | ctx: table. Context before layout generation |
| Return value | None. Any returned value is ignored |
| Invocation timing | Every frame, before Character and Part layout generation |
| Main modification targets | ctx.global / ctx.camera / ctx.shapes:declare / ctx.shape_cameras:declare / ctx.glyphs:declare |
| On error | On the host, skips later callbacks and does not render that frame (MT Sim continues previewing with the original values) |
Receives a working copy of the Global settings created from host parameters and modifies values before layout generation.
TIP
Design rule for scale adjustment: When scaling text to fit the frame based on text length or character count, adjust ctx.global.scale in OnPreLayout rather than shrinking each character's character.scale inside OnLayout. That keeps the overall text size steady instead of shifting every time a character appears during a typewriter-style reveal.
See ctx.timeline for time-axis design rules (real seconds vs. progress).
3.2 OnLayout(ctx)
| Item | Description |
|---|---|
| Signature | function OnLayout(ctx) |
| Argument | ctx: table. Context after layout generation |
| Return value | None. Any returned value is ignored |
| Invocation timing | Every frame, after Character and Part layout generation |
| Main modification targets | ctx.chars[index] / ctx.parts[index] / ctx.shapes[index] / ctx.shape_cameras[index] / ctx.bounding_box / ctx.output |
| On error | On the host, skips OnPath and does not render that frame (MT Sim continues previewing with the unmodified layout) |
Adjusts Characters, Parts, and Shapes generated from the finalized Global settings. The Character and Part counts are finalized at this point.
3.3 OnPath(ctx)
| Item | Description |
|---|---|
| Signature | function OnPath(ctx) |
| Argument | ctx: table. Context for path editing |
| Return value | None. Any returned value is ignored |
| Invocation timing | Every frame, after OnLayout and before deformation, projection, and rendering |
| Main modification target | The path of a Part or Shape obtained from ctx.paths |
| On error | On the host, does not render that frame (MT Sim continues previewing with the original glyph outlines) |
Use part / character / select / shape on ctx.paths to obtain target Parts and edit their path. For details of the retrieval methods and editing API, see the ctx reference.
3.4 Complete Script Example
-- @MugTypography
-- @api_level 8
-- Base phase: values that determine layout and global inheritance.
function OnPreLayout(ctx)
ctx.global.tracking = mt.wave(ctx.time, 0.25, 0.0) * 0.05
ctx.global.rotation = mt.wave(ctx.time, 0.1, 0.0) * 5.0
end
-- Layout phase: adjust only the generated characters and parts.
function OnLayout(ctx)
for index = 1, ctx.char_count do
local character = ctx.chars[index]
local progress = mt.stagger(ctx.time, index, 0.06, 0.35)
character.offset_y = 0.5 + (1.0 - mt.ease.out_cubic(progress)) * 0.15
character.rotation = character.rotation + mt.wave(ctx.time, 0.5, index * 0.25) * 3.0
end
for index = 1, ctx.part_count do
local part = ctx.parts[index]
part.scale = part.scale * (1.0 + mt.random(42, index) * 0.05)
end
endFor more practical examples, see the bundled samples/. samples/reference/ contains short, topic-grouped lessons for the scripting model and individual APIs, while samples/showcase/ is a collection of complete animations.
4. Types and Coordinate Systems
4.1 Definitions of Coordinates and Units
Public number fields belong to one of the following unit systems.
X components use canvas width as 1, and Y components use canvas height as 1. Positive Y is upward (§4.3).
canvas normalized position
Represents a position on the canvas. The value 0.5 is the canvas center on the corresponding axis.
| Field | Definition |
|---|---|
ctx.global.position_x / position_y | Global position. Writable (OnPreLayout) |
ctx.global.pivot_x / pivot_y | Displacement of the pivot for Global transformations relative to global.position. Writable (OnPreLayout) |
canvas coordinates in geometry (bounds_center_*, etc.) | Read-only positions in the pre-transformation layout. Different for each Character and Part |
canvas normalized displacement (neutral value 0.5)
Represents displacement relative to the natural position determined by the layout. It is not an absolute position. The neutral value is 0.5, and the rendered displacement is interpreted as follows:
Displacement (canvas ratio) = value - 0.5The displacement is based on canvas width or height, not character size or geometry.advance_*.
| Field | Definition |
|---|---|
Character/Part offset_x / offset_y | Translation from the natural position. At 0.5, displacement is 0 and the item remains at the position determined by the layout (its center before transformations is geometry.bounds_center_*). Values greater than 0.5 translate right/up. Use mt.layout.place_2d to place an item at a target 2D canvas position |
global.pivot_x / global.pivot_y | Displacement of the rotation/scale pivot from global.position. At 0.5, the pivot matches global.position |
Character/Part pivot_x / pivot_y | Displacement of the rotation/scale pivot from the natural center. At 0.5, the pivot matches the natural center (for Characters, geometry.bounds_center; for Parts, the natural position) |
Relationship between position and displacement:
| Type | Targets | Meaning of value | Neutral / center |
|---|---|---|---|
| Position | global.position_*, canvas coordinates in geometry | Coordinate relative to the canvas | 0.5 = canvas center |
| Displacement | global.pivot_*, Character/Part offset_* / pivot_* | Difference relative to global.position, the natural position, or the natural center | 0.5 = zero difference |
Other Unit Systems
| Unit system | Fields | Definition |
|---|---|---|
| em | tracking, margins, stroke.width, stroke.gap, shadow.distance | Ratio to font size. 1.0 = 1 em. Strokes are multiplied by fontSize during rendering |
| fontSize percentage | bounding_box.width / margin_* / corner_radius / fill_offset_* / stroke_offset_* | 100.0 = the same length as fontSize. Conversion to px is value * fontSizePx / 100 |
| Multiplier | scale, stretch_*, zoom | 1.0 is the original size |
| 0-1 | opacity, each color component, write_on_start / write_on_end, bounding_box.fill_opacity, etc. | Value interpreted as a closed interval or progress |
| degree | rotation, yaw, pitch, and various angle fields | Angle. See §4.3 for signs |
| Perspective strength | camera.perspective | Unitless. Internally mapped as distancePx = f(perspective) * canvas.height (larger values move the camera closer) |
| px | canvas.width / height | Pixels |
NOTE
shadow.distance is in em, like stroke.width, and is measured in each part's own space: the shadow keeps the same distance from the glyph however the part is scaled (global, character and part scale and stretch_*), rotated or seen in perspective. The direction set by shadow.angle stays fixed on the canvas.
4.2 color
{ r = 1.0, g = 0.5, b = 0.0, a = 1.0 }r / g / b / a are numbers in the range 0-1. You only need to include the channels you want to change (for example, { a = 0.5 }); any omitted keys keep their existing values on the target color. Adding any key other than r / g / b / a raises an error.
4.3 Coordinates
See §4.1 for the unit definitions of position, offset, and pivot
X is based on canvas width, and Y is based on canvas height
For
global.position, Character/Partoffset/pivot, and geometry, positive Y is upward (increasing the Lua Y value moves upward on screen)For 3D depth, positive
zpoints deeper (larger values move farther from the camera and place the item deeper into the screen. Negative values move it closer to the viewer)global.pivotand Character/Partoffset/pivotare canvas normalized displacements (§4.1). Geometry canvas coordinates andglobal.positionare canvas normalized positionsFor geometry canvas positions, the lower-left of the canvas is
0,0, and the center is0.5,0.5Geometry local positions and advances are displacement amounts, so they have no center offset and are represented as Y-up
Drawing coordinates use Y-down; placement coordinates, offsets and character metrics use Y-up
rotation, yaw, pitch, and angle are all in degrees
- 2D rotation (
rotation): Positive (+) is clockwise (right rotation), and negative (-) is counterclockwise (left rotation) (90turns what pointed up so it points right). - 3D object rotation (
yaw,pitch): For both Individual and Global, rotation occurs around the individual pivot.yaw: Positive (+) rotates the object's right edge toward the viewer (-Z) and its left edge deeper (+Z).pitch: Positive (+) rotates the object's top edge toward the viewer (-Z) and its bottom edge deeper (+Z).
- Camera orbit (
yaw,pitch): The camera orbits around the look-at point.yaw: Positive (+) moves the camera around to the right of the look-at point (the object on screen appears to rotate left).pitch: Positive (+) moves the camera upward around the look-at point (producing a downward-looking angle).
- Gradient angle (
angle): At0degrees it progresses from left to right (positive X direction), and increasing (positive) angles rotate it clockwise. - Shadow angle (
angle): At0degrees it extends rightward (positive X direction), and increasing (positive) angles rotate it clockwise (90degrees points straight down).
- 2D rotation (
WARNING
The 2D angle system consistently follows "screen intuition" (clockwise is positive, 90 degrees = straight down), which feels opposite to the coordinate system (Y-up). While positive offset_y points up, positive rotation / gradient.angle / shadow.angle rotates clockwise, and a shadow angle of 90 points straight down.
Deriving the sign from the coordinate system gives the opposite answer. Y-up normally implies counterclockwise-positive, which is what the mathematical convention says and what Fusion's own Transform node does. This effect is the exception, so an angle taken from either source needs its sign flipped, including when you combine this effect with a Transform node and want them to turn together.
Read the sign off a clock face. rotation = 90 turns what pointed up so it points right (12 o'clock → 3 o'clock). Watch the lower half, where the clock and the screen stop agreeing: a shape hanging down swings left under a positive value, since 6 o'clock advances toward 7 and 8.
When you turn an angle into a direction vector and feed it to offset_y, use mt.polar_offset_2d. Its input stays in the angle convention (90 degrees points down on screen), while the result uses the position convention (positive Y is upward), so it can be added directly to both offset_x and offset_y.
local dx, dy = mt.polar_offset_2d(angle, radius)
character.offset_x = character.offset_x + dx
character.offset_y = character.offset_y + dyTIP
Use mt.layout.radial_distance for distances in circular and radial effects. The canvas aspect ratio is corrected inside the function, so you do not need to transform the X coordinate manually.
local distance = mt.layout.radial_distance(
ctx,
geometry.bounds_center_x,
geometry.bounds_center_y
)To calculate a signed distance or dot product in a diagonal direction, multiply the X difference by ctx.canvas.aspect_ratio before the calculation. Also multiply by ctx.canvas.pixel_aspect_ratio when accounting for the physical display ratio of non-square pixels.
pivot Is a Displacement from the Reference Point
ctx.global.pivot_x/yis a displacement fromglobal.position. At0.5, 0.5, the pivot matchesglobal.position- Character/Part
pivot_x/yis a displacement from the item's own reference point. At0.5, 0.5, a Character rotates and scales around its natural bounding-box center (equivalent togeometry.bounds_center), while a Part rotates and scales around its natural position (equivalent togeometry.canvas_center) - Therefore, do not assign a canvas coordinate from
geometrydirectly to a Character or Part pivot. To rotate around a point shifted 10% to the right from the item's own center, usepivot_x = 0.5 + 0.1 - To use a Character's edge as its pivot, displace it from the natural center by half of its size (
bounds_width/bounds_heightare canvas-axis ratios and use the same unit system as pivot displacement):- Bottom edge:
pivot_y = 0.5 - geometry.bounds_height * 0.5 - Top edge:
pivot_y = 0.5 + geometry.bounds_height * 0.5 - Left edge:
pivot_x = 0.5 - geometry.bounds_width * 0.5 - Right edge:
pivot_x = 0.5 + geometry.bounds_width * 0.5
- Bottom edge:
4.4 Assignment Rules
The current value of a writable field contains the Inspector setting and any value finalized by an earlier callback. Unless a complete override is part of the effect's design, compose animation over that current value.
- Add animated deltas to position, offset, pivot, rotation, and depth values
- Multiply scale, stretch, and opacity by animated factors
- Direct assignment is appropriate when replacing a style such as a fill or stroke color is the effect's explicit purpose
- If several calculations need an original value, save it to a local variable before the first write
local inspectorRotation = character.rotation
local inspectorScale = character.scale
character.rotation = inspectorRotation + math.sin(ctx.time) * 10.0
character.offset_y = character.offset_y + math.sin(ctx.time * 2.0) * 0.05
character.scale = inspectorScale * (1.0 + math.sin(ctx.time) * 0.1)
character.opacity = character.opacity * fade
-- Assign directly only when replacing the Inspector setting is intentional
character.rotation = ctx.time * 20.0The following operations cause errors:
- Assigning to a non-writable field
- Adding an unknown field
- Type mismatch (such as assigning a string where a number is expected)
- Replacing or changing the number of elements in read-only containers such as
ctx.chars/ctx.parts - Assigning an invalid string to an enumerated field
5. Inheritance Mechanism
Roles of use / opacity / fill.transparent
| Setting | Effect on rendering |
|---|---|
fill.use / stroke.use / shadow.use | When true, uses the individual values set on the target Character or Part; when false, inherits the parent settings. Does not toggle visibility |
opacity (Global / Character / Part) | Multiplies the opacity of the fill, stroke, shadow, and other values determined after inheritance. 0 is transparent, and 1 preserves the original opacity |
global.fill.transparent | Makes only the fill transparent. Does not affect the stroke or shadow |
The color and opacity used for rendering are determined in the following order.
- Determine color: If
fill.useistrue, use the individual color set on the Character or Part; iffalse, use the gradient (when enabled) or the Global fill color - Apply opacity: Multiply the opacity of the determined color by
global.opacity × character.opacity × part.opacity
When use = false, modifying the individual color of a Character or Part does not make that color affect rendering. Changing only opacity changes only the opacity without changing the color or gradient being used.
Values used for rendering are determined in the following order.
Global finalized in OnPreLayout
↓
Character: if each group's use is true, use the Character's individual value; if false, use the Global value
↓
Part: if each group's use is true, use the Part's individual value; if false, use the value selected at the Character stageWhen gradient.color_space is anything other than none, only the areas for which the Global value was selected in this order (where both the Character and Part fill.use values are false) are filled with the gradient. Characters or Parts with individual colors enabled are filled with their individual solid colors.
A Part's stroke / shadow follows the same order as fill when selecting the value to use, except that the host has no corresponding parameter groups for Parts (they always begin unused/with default values).
Characters and Parts passed to OnLayout contain individual settings before parent settings are applied. Lua can also modify the use field of each group, so an individual value can be modified and enabled within the same callback.
local character = ctx.chars[index]
character.fill.use = true
character.fill.color = mt.color.from_hsv(index / ctx.char_count, 0.8, 1.0)Transform fields (offset / scale / rotation, etc.) have no use_* field and always affect rendering. Script modifications are confined to that frame and are never written back to the host Inspector or keyframes.
6. Execution Environment, Errors, and Limits
6.1 Frame Handling on Errors
On the host, an error anywhere in a callback causes that frame not to be rendered. Changes made before the failure are not guaranteed to be rolled back, but an incomplete result is never used for rendering.
To keep debugging convenient, MT Sim reports the error in the Output panel and continues previewing with values from before the failed callback. This differs from the host's frame-abort behavior.
| Failure location | Result |
|---|---|
| Script loading | The host does not render; MT Sim reports the error and retains the last valid script |
OnPreLayout | The host skips later callbacks and does not render; MT Sim previews the original values |
OnLayout | The host skips OnPath and does not render; MT Sim previews the unmodified layout |
OnPath | The host does not render; MT Sim previews the original glyph outlines |
6.2 Available Standard Libraries
The Lua standard libraries are limited to a safe subset.
- Available: Safe basic functions,
math,string,table,utf8 - Unavailable:
os,io,package,require,dofile,loadfile,load,debug,collectgarbage,rawget,rawset,getmetatable,setmetatable
Files, networks, and processes cannot be accessed.
6.3 Execution Limits
- 5,000,000 instructions per callback invocation
- Memory limit of 128 MiB (during host execution)
A callback that exceeds a limit is treated as failed and follows the behavior described in §6.1.
6.4 print(...) and Frame Dump
Lua's standard print(...) can be used to output diagnostic information to the Output panel in MT Sim.
Frame Dump
Clicking the Bug icon (Frame Dump) button on the simulator toolbar evaluates the marked timeline range (or all frames) in one pass and dumps scene context (resolution, character/part count, font, etc.) along with each frame's print(...) output and errors to the log.
=== SIMULATOR CONTEXT ===: Scene context including canvas size, fps, character count, part count, font, and layout mode=== FRAME LOGS ===: Per-frame[<frame>] <text>output (frames that print nothing are omitted)=== DUMP COMPLETE ===: Evaluation summary- You can narrow down the verification range by dragging the timeline in/out handles.
6.5 Checking Errors
Script error details cannot be checked in the host. First run the script in MT Sim, then check and fix the error message and stack trace displayed in the Output panel.