VEXpression
Neonyte has two VEX hook parameters. Both run once per sign. Between them they cover the whole pipeline: before the planner has made a decision, and after it has made every decision.
Where the two hooks run
This is the order geometry passes through the node.
placeholder boxes
-> ANCHORS one point per sign, 9 attributes
-> anchor_vex PRE-PLAN VEXpression (parm: vex_anchor)
-> sign_plan the planner
-> SLOTS one point per sign, 134 attributes
-> plan_vex POST-PLAN VEXpression (parm: vex_override)
-> PLAN
-> neon geometry, then hardware geometry
The pre-plan VEXpression runs before the planner has made any decisions: at that point every sign is still a single anchor point, and business type, name, layout, colours, hardware and wear are all still open. The post-plan VEXpression runs after every decision the planner has made, on the finished plan, before any geometry is built.
Hardware is built after plan_vex runs, but it is still reachable from it. Every hardware decision is stamped onto the plan as an hw_* attribute before the hardware geometry is generated from it, so writing to an hw_* attribute here changes what gets built.
Pre-plan: vex_anchor
This runs on ANCHORS, one point per sign, before the planner runs. It is the earliest point in the pipeline where you can touch a sign, and the only point where you can steer a decision before the planner makes it rather than overwrite the result afterward.
From here you can reach decisions made inside the planner: business type, name form, era, script, palette, font, layout, theme, glyph substitution, and decay. You reach them through 29 force hooks - one per Advanced row that has a VEXpression column in the master matrix. The force hooks are derived directly from the 52 Advanced rows, so the two cannot drift apart.
The 9 attributes available on ANCHORS:
The 29 force hooks:
Every force attribute is a string
This includes the numeric ones. It is deliberate, and it matters.
A bound VEX write binds at compile time. If you write if (@piece == 3) f@force_icon = 0.0;, VEX creates f@force_icon on every point, not just point 3 - and every point that does not take the if branch is left at the float default of 0.0. That silently strips icons off every other sign on the street.
A string attribute defaults to empty, and empty means "the engine decides". So the same conditional write, done as a string, is safe by construction: points that do not take the branch stay empty, and empty is a no-op.
Always write s@force_..., and always assign a string - even for a value that looks numeric.
An unparseable value is ignored, never guessed at. If you misspell a value, the hook does nothing and the engine falls back to its own decision - it does not try to coerce your typo into the closest plausible value. A typo stays visible as "nothing changed" rather than silently picking something else.
Verified example - this one produced a Japanese bar sign titled 焼鳥 とり吉:
s@force_type = "bar";
s@force_script = "ja";
s@force_colors = "red";
Per-sign, using the anchor's own @piece:
if (@piece == 3) {
s@force_type = "ramen";
s@force_era = "1980s";
}
Post-plan: vex_override
This runs on the finished plan, one point per sign, after every decision the planner has made and before any geometry is built. It sees the whole plan: 134 attributes covering neon and lettering, hardware, content and placement.
Normal VEX typing applies here - i@, f@, v@, s@ - because these attributes already exist on every point by the time this hook runs. The compile-time binding trap described above does not apply on this hook.
Hardware is built after this hook runs, but it is reachable from it: every hardware decision is stamped onto the plan as an hw_* attribute before geometry is generated from it, so writing to an hw_* attribute here changes what gets built.
Neon and lettering (slot_*)
33 attributes.
Hardware (hw_*)
85 attributes.
Attributes with a _ctl row in the Advanced section take the same values as that row's menu.
Content (sign_*)
5 attributes.
Placement and identity
Verified examples:
f@slot_emit *= 3.0; // three times the emission
i@hw_l1 = 0; // strip the backing panel
i@hw_cab_n = 0; // and the cable runs
Both hooks
Both parameters carry the stock attribwrangle snippet tags, including the button that creates spare parameters from every ch() call in the expression. So writing chramp("falloff", x) in either hook gives you a real ramp control on the node.
There is no output 2. The node has one output. To see what the plan holds at any point, read the attributes listed on this page rather than looking for a debug stream.