Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Naming Convention Redesign

Status: Draft

Rationale

The current ɴsɪ attribute names grew organically and suffer from several inconsistencies that make the API harder to learn and use than it needs to be.

No word separation. Most multi-word names are run together, forcing users to mentally parse where words begin and end — and the conventions are unpredictable:

NameIntended meaning
numberofthreadsnumber of threads
texturememorytexture memory
renderatlowpriorityrender at low priority
clockwisewindingclockwise winding
importancesamplefilterimportance sample filter
unitlengthmillimetersunit length millimeters

Inconsistent grouping. Some attributes use dot-separated groups, others don’t — even for closely related settings:

GroupedNot grouped
subdivision.cornerverticesclockwisewinding
subdivision.creasesharpnessoutlinecreasethreshold
visibility.camerasurfaceshader
quality.shadingsamplestexturememory
show.displacementrenderatlowpriority

Cryptic abbreviations alongside verbose names. Single-letter names coexist with long compound words:

TerseVerbose
Ptransformationmatrices
Nemissionintensitygrid
fovquality.samplevolumeemission
idstoppedcallbackdata

Node type names baked into attributes. Some attributes redundantly include the node type, others don’t:

RedundantClean
shaderfilename (on shader node)filter (on outputlayer node)
shaderobject (on shader node)angle (on environment node)
vdbfilename (on volume node)width (on particles node)
imagefilename (on outputdriver node)basis (on curves node)

This document proposes a systematic naming convention that resolves these inconsistencies. The complete mapping from current to proposed names is in the attribute mapping below.

Why Separate Words at All?

Concatenated names like maximumraylength or importancesamplefilter are hard to read — especially for non-native English speakers, who may not immediately see where one word ends and the next begins. Word separators make attribute names accessible to a wider audience without any downside: attribute name strings are interned by the renderer, so separators have zero runtime cost. They add a few bytes to the source but nothing to render time.

A common objection is that separators mean more typing. In practice this matters less than it used to: code is increasingly written with AI assistance and autocompletion, so keystroke count is a non-issue. What matters is how easily a human can read and review the code — and depth-of-field.focal-length is unambiguously clearer than depthoffield.focallength.

Redesign Proposal

Hyphenate Multi-Word Attributes

Attribute names use hyphens (-) as word separators, not underscores (_). This is a deliberate choice: almost no programming language allows hyphens in identifiers (variable-name is invalid in C, C++, Python, Rust, Lua, etc.), so attribute name strings are instantly distinguishable from code identifiers in any language. When you see reflection.ray-depth-max in source code, it can only be an attribute name — never a variable, function, or type.

Core Convention

  • . (dots) separate hierarchy levels — these correspond to what would be groups/rollouts/sections in UI/attribute editor.
  • - (hyphens) separate words within a single label.
  • Singular nouns when used as modifiers in compound names (English compound noun rule).
    • Example: point-grid not points-grid — “point” modifies “grid”.
  • Plain English over jargon (governing principle — R9).
    • Example field-of-view not fov.
  • Compound node type names also use hyphens: vdb-particles, output-driver, output-layer, face-set, perspective-camera, fisheye-camera, etc.
  • Example: subdivision.corner-sharpness — group “Subdivision”, label “Corner Sharpness”.

Rulings

R1: Type-first grouping for ray settings

Ray depth/length settings on the global node are grouped by ray type, since each type has multiple related attributes (depth + length):

reflection.ray-depth-max
reflection.ray-length-max
diffuse.ray-depth-max
diffuse.ray-length-max

Only use dot-separated hierarchy when there are 2 or more related attributes that form a logical group. Single standalone attributes use flat hyphenated naming:

Groups (dot-separated):

subdivision.scheme
subdivision.corner-index
subdivision.corner-sharpness
visibility.camera
visibility.diffuse
visibility.reflection

Flat (hyphen-separated):

vertex-count
hole-count
clockwise
reference-time
quadratic-motion

R3: The grouping level is context-dependent, not concept-fixed

The same concept (e.g., “reflection”) can be a group in one context and a leaf in another. The rule is: whichever level has 2+ siblings becomes the group.

On the global node — each ray type has 2+ settings (depth + length), so the ray type is the group:

reflection.ray-depth-max
reflection.ray-length-max

On the attributes node — each ray type has only ONE visibility flag, but “visibility” has 8+ flags, so visibility is the group:

visibility.reflection
visibility.diffuse
visibility.camera

The alternative (type-first everywhere: reflection.visibility, diffuse.visibility, etc.) would create 8 singleton groups, violating R2. The 2+ rule takes precedence over concept consistency.

R4: Node type is an implicit top-level group

The node type itself provides context, so attribute names should not redundantly include the node type. On a curves node, the attribute is basis, not curve-basis. On a particles node, the attribute is id, not particle-id.

R5: Rename everything, including legacy single-letter names

No grandfather clause for industry-standard abbreviations:

  • Pposition
  • Nnormal
  • nverticesvertex-count
  • nholeshole-count

Single-word names that are already clear stay as-is: width, basis, id, matte, clockwise.

R6: Hyphen-separate compound domain terms

No concatenated words. All multi-word terms get hyphens:

  • depthoffielddepth-of-field
  • fstopfocal-stop
  • focallengthfocal-length
  • focaldistancefocal-distance

This applies within both group names and leaf labels:

depth-of-field.enable
depth-of-field.focal-stop
depth-of-field.focal-length
depth-of-field.aperture.enable    ← sub-group (3 attrs: enable, sides, angle)
depth-of-field.aperture.sides
depth-of-field.aperture.angle

R7: Connection attribute plurality matches cardinality

If a connection attribute accepts multiple connections, use plural. If it accepts only one, use singular.

Plural (multi-connection):

objects              (root, transform — multiple geometry nodes)
attributes           (root, transform — multiple attribute nodes)
members              (set — multiple objects)
screens              (camera — multiple screen nodes)
output-layers        (screen — multiple layer nodes)
output-drivers       (output-layer — multiple driver nodes)

Singular (single-connection):

shader.surface       (attributes — one surface shader)
shader.displacement  (attributes — one displacement shader)
shader.volume        (attributes — one volume shader)
background-layer     (output-layer — one background layer)

R8: Group by concern, not by concept

Attributes are grouped by what kind of setting they are, not by what rendering concept they relate to. This keeps groups semantically coherent:

  • quality.* = how much effort the renderer spends (sampling counts, performance)
  • shading.* = which shading features are enabled/disabled (feature toggles)
  • {type}.* = per-ray-type limits (depth, length)
quality.shading-samples           ← sampling effort
quality.volume-samples            ← sampling effort
quality.denoise                   ← quality toggle
quality.volume-emission-sampling  ← quality toggle
quality.preview.global-update     ← preview/IPR quality
quality.preview.interpolate       ← preview/IPR quality
quality.preview.speed-multiplier  ← preview/IPR quality

shading.displacement              ← feature toggle
shading.atmosphere                ← feature toggle
shading.multiple-scattering       ← feature toggle
shading.osl-subsurface            ← feature toggle

volume.ray-depth-max              ← ray limit
volume.ray-length-max             ← ray limit

This avoids scattering quality-related attrs across concept groups (which would make it hard to find “all the knobs that affect render speed”).

R9: Plain English over jargon (governing principle)

This is a governing principle that applies across all naming decisions. When choosing between a technical abbreviation/jargon term and a plain English equivalent, always prefer plain English. Names should be understandable without domain-specific knowledge.

  • iprpreview (Interactive Progressive Rendering → just “preview”)
  • fovfield-of-view
  • fstopfocal-stop (kept because it’s the actual name of the unit, not jargon)

The group quality.ipr.* becomes quality.preview.*:

quality.preview.global-update
quality.preview.interpolate
quality.preview.speed-multiplier

R10: Use .enable suffix for booleans in mixed groups

When a boolean on/off attribute belongs to a group that also has non-boolean attrs, append .enable to distinguish the toggle from the group:

depth-of-field.enable         ← mixed group (has focal-stop, focal-length, etc.)
depth-of-field.focal-stop
depth-of-field.focal-length

cryptomatte.enable            ← mixed group (has level)
cryptomatte.level

When the group is all-toggles or the boolean is standalone, no .enable needed:

shading.displacement          ← all-toggle group, obviously a toggle
shading.atmosphere

quality.denoise               ← obviously a toggle from context

R11: Unify callbacks under callback.* group

All callback/handler function pointers across the API follow a consistent pattern: callback.{purpose} for the function pointer and callback.{purpose}.data for the associated userdata.

This applies across different API calls — the callback group is a cross-cutting convention:

# NSIBegin
callback.error               ← error handler function (was: errorhandler)
callback.error.data          ← error handler userdata (was: errorhandler.data)

# NSIRenderControl
callback.stop                ← stopped callback function (was: stoppedcallback)
callback.stop.data           ← stopped callback userdata (was: stoppedcallbackdata)

R12: Singular nouns as modifiers in compound names

When a noun serves as a modifier (adjective) in a compound name, use its singular form. This follows standard English compound noun formation: “dog house” not “dogs house”, “vertex count” not “vertices count”.

vertex-count         ✓  (not vertices-count)
hole-count           ✓  (not holes-count)
face-index           ✓  (not faces-index)
point-grid           ✓  (not points-grid)
object-index         ✓  (not objects-index)

Plurals are reserved for R7 (connection cardinality), where the attribute name IS the thing, not a modifier:

objects              ✓  (the attribute is multiple objects, not a modifier)
members              ✓
output-layers        ✓

Open Question: Node Granularity

The convention above renames attributes consistently, but it doesn’t resolve a deeper inconsistency in how ɴsɪ models primitives. Three different patterns are in play across the existing node types:

PrimitivePatternExamples
MeshesOne node, type attributemesh (polygons and Catmull-Clark via subdivision.scheme)
VolumesOne node, but only one backendvolume (renders OpenVDB exclusively)
ParticlesSplit by backendparticles, vdbparticles
CamerasSplit by projectionperspectivecamera, fisheyecamera, cylindricalcamera, sphericalcamera, orthographiccamera

mesh collapses polygons and subdivision surfaces behind a subdivision.scheme attribute. Cameras do the opposite — five separate node types that differ only in their projection function. Volumes name themselves generically while only one backend is implemented. Particles are split by the data format their control points hold, not by what the renderer sees.

Three coherent resolutions, from least to most invasive:

Option 1 — Honest names, same shape

Keep one node per concern and rename for honesty. The mapping later in this document already adopts these names:

  • volumevdb-volume (it only renders OpenVDB).
  • vdbparticlesvdb-particles (hyphenated).
  • Cameras keep their five hyphenated names (perspective-camera, …).
  • mesh stays as the one merged exception.

The inconsistency with mesh remains. This is a pure rename — no API change.

Option 2 — Collapse to one canonical primitive

Bring volumes, particles, and cameras in line with mesh’s “one node, type attribute” pattern:

  • vdb-volume and vdb-particles collapse into a single vdb node with kind = "volume" | "particles" (or distinguished by which data attribute is supplied).
  • The five camera nodes collapse into one camera node with projection = "perspective" | "fisheye" | "cylindrical" | "spherical" | "orthographic". Projection-specific attributes live behind the projection’s prefix (e.g. fisheye.mapping).

Every scene-graph entity ends up with a single canonical primitive. Migration is “rename node type, add a kind / projection attribute”. The renderer’s dispatch table has to flatten, but no user-side attribute is lost.

Option 3 — Split mesh to match the rest

Adopt the honest renames from Option 1 — volumevdb-volume, vdbparticlesvdb-particles — and additionally replace mesh with polygon-mesh and subdivision-mesh. Each mesh node carries only the attributes meaningful for its surface kind; subdivision.scheme disappears entirely.

This is the most invasive option: every existing scene using subdivision surfaces has to re-target the new node, and the subdivision.* attributes migrate from prefix-grouped on mesh to top-level on subdivision-mesh.

Trade-offs

  • Option 1 is cheapest to deliver; it preserves the inconsistency under prettier names.
  • Option 2 matches the mesh pattern. Requires backend dispatch work but no user-facing data loss.
  • Option 3 is the cleanest in isolation but invalidates the largest existing-asset footprint.

No recommendation in this draft. The decision belongs in the API roadmap, not in a renaming pass.

Open Question: ᴏsʟ Built-In Variable Alignment

ɴsɪ is designed to feed ᴏsʟ shaders. When the renderer puts the control-point position into an attribute named P on a mesh node, an ᴏsʟ shader reads it as the built-in global variable P without any plumbing in between. That 1:1 mapping is part of what makes ᴏsʟ shaders portable across renderers, and it directly conflicts with R5.

R5 currently renames the legacy single-letter attribute names:

CurrentR5 proposalᴏsʟ built-in
Ppositionpoint P
Nnormalnormal N

Adopting R5 as written means the attribute name and the ᴏsʟ global diverge. The renderer either has to translate positionP at the ᴏsʟ binding step — hidden machinery that surprises anyone debugging by attribute name — or the cross-renderer ᴏsʟ contract has to break for ɴsɪ specifically.

The ᴏsʟ globals that overlap with current ɴsɪ attribute names are: P, N, Ng, u, v, dPdu, dPdv, I.

Option A — Carve out an exception in R5

Legacy single-letter names that match an ᴏsʟ global stay as-is, even where the surrounding convention would rename them. This preserves the ɴsɪ ↔ ᴏsʟ alignment.

Affected rows revert in the rename mapping:

  • mesh: P stays.
  • nurbs: P stays. Pw stays (semantics flow into the same ᴏsʟ binding).
  • curves: P stays.
  • particles: P stays, N stays.

nvertices, nholes, clockwisewinding etc. still rename — they aren’t ᴏsʟ globals.

Option B — Rename and translate

Adopt R5 as written. The renderer translates positionP (and normalN, …) when binding attributes to ᴏsʟ shader globals.

This keeps ɴsɪ-level naming uniform but introduces hidden translation. Shader authors writing portable code now read about P in the ᴏsʟ docs and position in the ɴsɪ docs and have to internalise the mapping. Debugging tools that show attribute names won’t match what the shader sees.

Trade-off

The decision turns on which contract matters more:

  • ᴏsʟ portability (Option A) — the convention that “the attribute named P is what the shader reads as P” is sacred.
  • ɴsɪ-internal consistency (Option B) — single-letter names are jargon and R5’s reasoning applies uniformly; the ᴏsʟ binding layer can absorb the cost.

No recommendation in this draft.

Complete Attribute Mapping

Every attribute across all node types, with current → new name and the ruling(s) that apply. Attributes where current = new are omitted.

global Node

CurrentNewRules
numberofthreadsthread-countR2 (1 attr, flat), R5, R6
texturememorytexture-memoryR2 (1 attr, flat), R6
networkcache.sizenetwork-cache.sizeR6
networkcache.directorynetwork-cache.directoryR6
networkcache.mipmapnetwork-cache.mipmapR6
networkcache.writenetwork-cache.writeR6
renderatlowprioritylow-priorityR6, R9
bucketorderbucket-orderR6
hidemessagesmessages.hideR2 (2 message attrs: hide + timestamp)
maximumraydepth.diffusediffuse.ray-depth-maxR1
maximumraydepth.hairhair.ray-depth-maxR1
maximumraydepth.reflectionreflection.ray-depth-maxR1
maximumraydepth.refractionrefraction.ray-depth-maxR1
maximumraydepth.volumevolume.ray-depth-maxR1
maximumraylength.diffusediffuse.ray-length-maxR1
maximumraylength.hairhair.ray-length-maxR1
maximumraylength.reflectionreflection.ray-length-maxR1
maximumraylength.refractionrefraction.ray-length-maxR1
maximumraylength.specularspecular.ray-length-maxR1 (pattern consistency)
maximumraylength.volumevolume.ray-length-maxR1
quality.denoisequality.denoiseR8
quality.iprglobalupdatequality.preview.global-updateR8, R9
quality.iprinterpolatequality.preview.interpolateR8, R9
quality.iprspeedmultiplierquality.preview.speed-multiplierR8, R9
quality.shadingsamplesquality.shading-samplesR8, R6
quality.volumesamplesquality.volume-samplesR8, R6
quality.samplevolumeemissionquality.volume-emission-samplingR8, R6
referencetimereference-timeR6
show.displacementshading.displacementR8
show.atmosphereshading.atmosphereR8
show.multiplescatteringshading.multiple-scatteringR8, R6
show.osl.subsurfaceshading.osl-subsurfaceR8, R6
exclusiveshadingexclusive-shadingR6
messages.timestampmessages.timestamp

Unchanged: license.server, license.wait, license.hold, frame, statistics.progress, statistics.filename, verbose

root Node

CurrentNewRules
geometryattributesattributesR6, R7 (multi-conn → plural)

Unchanged: objects

set Node

Unchanged: members

mesh Node

CurrentNewRules
PpositionR5
nverticesvertex-countR5, R6
nholeshole-countR5, R6
clockwisewindingclockwiseR6 (simplification)
subdivision.cornerverticessubdivision.corner.indexR2 (3 corner attrs → sub-group)
subdivision.cornersharpnesssubdivision.corner.sharpnessR2
subdivision.smoothcreasecornerssubdivision.corner.automaticR2
subdivision.creaseverticessubdivision.crease.indexR2 (2 crease attrs → sub-group)
subdivision.creasesharpnesssubdivision.crease.sharpnessR2
referencetimereference-timeR6
quadraticmotionquadratic-motionR6
outlinecreasethresholdoutline-crease-thresholdR6

Unchanged: subdivision.scheme

nurbs Node

The u and v axes each have five related attributes (count, order, knot, min, max), so per R3 each axis becomes a group.

CurrentNewRules
nuu.countR3 (axis group), R5, R6
nvv.countR3 (axis group), R5, R6
uorderu.orderR3, R6
vorderv.orderR3, R6
uknotu.knotR3, R6
vknotv.knotR3, R6
uminu.minR3, R6
umaxu.maxR3, R6
vminv.minR3, R6
vmaxv.maxR3, R6
PpositionR5
Pwweighted-positionR5, R6 (alternative to position)
trimcurves.nloopstrim-curves.loop-countR5, R6
trimcurves.ncurvestrim-curves.curve-countR5, R6
trimcurves.ntrim-curves.point-countR5 (n and cv are jargon), R9
trimcurves.ordertrim-curves.orderR6 (group prefix only)
trimcurves.knottrim-curves.knotR6
trimcurves.mintrim-curves.minR6
trimcurves.maxtrim-curves.maxR6
trimcurves.u, .vtrim-curves.positionAPI change (consolidation); mirrors surface
trimcurves.u, .v, .wtrim-curves.weighted-positionAPI change (consolidation); mirrors surface
trimcurves.sensetrim-curves.senseR6

Consolidation note (API change, not pure rename). The current API stores trim-curve control points as three parallel arrays (trimcurves.u, trimcurves.v, trimcurves.w) — a structure-of-arrays layout. The surface stores its control points as one interleaved array (P or Pw) — an array-of-structures layout. The redesign aligns the two:

  • trim-curves.positionfloat[2], non-rational (u, v) pairs. Replaces trimcurves.u and trimcurves.v.
  • trim-curves.weighted-positionfloat[3], rational (u, v, w) triples. Replaces trimcurves.u, trimcurves.v, and trimcurves.w.

Supply one of the two; never both. This is the same supply-one-of pattern the surface uses for position / weighted-position.

face-set Node

CurrentNewRules
facesface-indexR9 (descriptive)

curves Node

CurrentNewRules
nverticesvertex-countR5, R6
PpositionR5

Unchanged: width, basis, extrapolate

particles Node

CurrentNewRules
PpositionR5
NnormalR5
reverseorientationreverse-orientationR6
quadraticmotionquadratic-motionR6

Unchanged: width, id

procedural Node

CurrentNewRules
boundingboxbounding-boxR6

environment Node

Unchanged: angle

shader Node

CurrentNewRules
shaderfilenamefilenameR4 (node type provides context)
shaderobjectobjectR4

attributes (geometry) Node

CurrentNewRules
surfaceshadershader.surfaceR2, R7 (single-conn → singular)
displacementshadershader.displacementR2, R7
volumeshadershader.volumeR2, R7
visibility.set.subsurfacevisibility.subsurface-setR6
regularemissionemission.regularR2 (2 emission attrs → group)
quantizedemissionemission.quantizedR2

Unchanged: ATTR.priority, visibility.camera, visibility.diffuse, visibility.hair, visibility.reflection, visibility.refraction, visibility.shadow, visibility.specular, visibility.volume, visibility, matte, bounds

transform Node

CurrentNewRules
transformationmatrixmatrixR4
geometryattributesattributesR6, R7
shaderattributesshader-attributesR6

Unchanged: objects

instances Node

CurrentNewRules
sourcemodelsobjectsR7 (multi-conn), R9
transformationmatricesmatricesR4, R6
modelindicesobject-indexR6, R9
disabledinstancesdisabled-indexR6

output-driver Node

CurrentNewRules
drivernamedriver-nameR6
imagefilenamefilenameR4
embedstatisticsembed-statisticsR6

output-layer Node

CurrentNewRules
variablenamevariable-nameR6
variablesourcevariable-sourceR6
layernamelayer-nameR6
scalarformatscalar-formatR6
layertypelayer-typeR6
colorprofilecolor-profileR6
withalphawith-alphaR6
sortkeysort-keyR6
lightsetlight-setR6
lightsetnamelight-set-nameR6
outputdriversoutput-driversR6, R7
filterwidthfilter.widthR2 (2 filter attrs → group)
filterfilter.nameR2
backgroundvaluebackground.valueR2 (2 background attrs → group)
backgroundlayerbackground.layerR2, R7 (single-conn)
lightdepthlight-depthR6

Unchanged: dithering, cryptomatte.enable, cryptomatte.level

screen Node

CurrentNewRules
outputlayersoutput-layersR6, R7
prioritywindowpriority-windowR6
screenwindowscreen-windowR6
pixelaspectratiopixel-aspect-ratioR6
staticsamplingpatternstatic-sampling-patternR6
importancesamplefilterimportance-sample-filterR6

Unchanged: resolution, oversampling, crop, overscan

vdb-particles Node

CurrentNewRules
vdbfilenamefilenameR4
pointsgridpoint-gridR6, R12
velocityreferencetimevelocity.reference-timeR2 (2 velocity attrs → group)
velocityscalevelocity.scaleR2
enablepscaleuse-point-scaleR9 (plain English)
widthscalewidth-scaleR6

Unchanged: width

volume Node

CurrentNewRules
vdbfilenamefilenameR4
densitygridgrid.densityR2 (6 grid attrs → group)
colorgridgrid.colorR2
emissiongridgrid.emissionR2
emissionintensitygridgrid.emission-intensityR2, R6
temperaturegridgrid.temperatureR2
velocitygridgrid.velocityR2
velocityreferencetimevelocity.reference-timeR2
velocityscalevelocity.scaleR2

Camera Nodes (perspective-camera, fisheye-camera, cylindrical-camera)

Common (all cameras):

CurrentNewRules
screensscreensR7
shutterrangeshutter.rangeR2 (2 shutter attrs → group)
shutteropeningshutter.openingR2
clippingrangeclipping-rangeR6

perspective-camera:

CurrentNewRules
fovfield-of-viewR9
depthoffield.enabledepth-of-field.enableR6, R10
depthoffield.fstopdepth-of-field.focal-stopR6
depthoffield.focallengthdepth-of-field.focal-lengthR6
depthoffield.focallengthratiodepth-of-field.focal-length-ratioR6
depthoffield.focaldistancedepth-of-field.focal-distanceR6
depthoffield.aperture.enabledepth-of-field.aperture.enableR6, R10
depthoffield.aperture.sidesdepth-of-field.aperture.sidesR6
depthoffield.aperture.angledepth-of-field.aperture.angleR6
unitlengthmillimetersunit-length-millimetersR6

fisheye-camera:

CurrentNewRules
fovfield-of-viewR9

Unchanged: mapping

cylindrical-camera:

CurrentNewRules
fovfield-of-view.verticalR2 (2 fov attrs → group), R9
horizontalfovfield-of-view.horizontalR2, R6, R9
eyeoffseteye-offsetR6

API Parameter Mapping

NSIBegin

CurrentNewRules
streamfilenamestream.filenameR2 (4 stream attrs → group)
streamformatstream.formatR2
streamcompressionstream.compressionR2
streampathreplacementstream.path-replacementR2, R6
separateprocessseparate-processR6
errorhandlercallback.errorR11 (unified callback group)
errorhandler.datacallback.error.dataR11
executeproceduralsevaluate-replaceR9

Unchanged: type

NSIDelete

Unchanged: recursive

NSIConnect

Unchanged: value, priority, strength

NSIEvaluate

CurrentNewRules
backgroundloadbackground-loadR6

Unchanged: type, filename, script, buffer, size

NSIRenderControl

CurrentNewRules
stoppedcallbackcallback.stopR11 (unified callback group)
stoppedcallbackdatacallback.stop.dataR11

Unchanged: action, progressive, interactive, frame

Open question — action as a positional argument: Every meaningful NSIRenderControl call needs an action value — one of start, wait, synchronize, suspend, resume, stop. The call is a no-op without it. Yet the current signature carries action inside the optional-parameter bag, peer to the genuinely-optional progressive / interactive / callback parameters.

Promoting action to a required positional argument would make intent visible at the call site:

typedef enum {
    NSIRenderStart,
    NSIRenderWait,
    NSIRenderSynchronize,
    NSIRenderSuspend,
    NSIRenderResume,
    NSIRenderStop,
} NSIRenderAction;

void NSIRenderControl(
    NSIContext_t        ctx,
    NSIRenderAction     action,
    int                 nparams,
    const NSIParam_t   *params);

A string overload can be kept for the Lua and Python bindings, where "start" / "wait" / "stop" read idiomatically. The enum form catches typos at compile time and surfaces the closed set of allowed values to IDE completion.

If adopted, action leaves this rename table entirely — there is nothing left to rename.