DOCUMENTATION

Safety and Write Modes

MifBridge can change your project, so it has a safety gate: a write mode that decides which operations may run. A person sets it, and the agent it restrains has no way to change it.

The three modes

Mode What runs
scratch (the default) Reads and writes run. Operations that no path check can make safe are refused.
read Today this behaves exactly like scratch. The per-endpoint read/write split that would make it stricter is not built yet, so do not rely on it to block writes.
full Everything, including saves, Play-In-Editor, cooks and console commands.

self_audit reports the live mode (writeMode) and whether the gate is active (safetyGateActive, true in every mode except full). A caller can check before it tries, rather than learning from a refusal.

What scratch refuses

Operations that persist to disk, take over the editor loop, run code outside the bridge, or can reach one of those without doing it themselves. The current list:

Group Endpoints
Persist to disk save_package, save_blueprint, save_dirty_packages, save_level_as, save_input_settings
Save files the engine writes on its own, or wider than you named run_retarget, pack_level_actor, scaffold_level, add_lighting_variant, create_ml_deformer, rename_asset, move_folder, fixup_redirectors, fix_up_redirectors, consolidate_assets, source_control_checkout
Write a collection's file create_collection, destroy_collection, add_to_collection, remove_from_collection
Take the editor loop start_pie, stop_pie, render_sequence, cancel_sequence_render, render_movie_capture
Execute outside the bridge run_console, exec_console, run_console_captured, trigger_cook
Replace the working set new_level, load_level
Long, unsupervised writes into the project build_navmesh, import_asset, import_vdb_as_volume_texture
Reach a save or the console without being one send_editor_key, invoke_editor_command, answer_modal_dialog, arm_modal_answer, ui_scenario_start, ui_scenario_activate

The previews of the gated bulk writes still answer in every mode: dryRun: true on rename_asset, fix_up_redirectors or add_to_collection lists what the call would change, and writes nothing.

The list also reserves save_all, quit_editor and restart_editor. No endpoint has those names today, so one added under any of them is refused from its first build.

The plugin holds the authoritative list, and a test derives part of it from the source, so a new endpoint that reaches the console is caught the day it is written. A refused call answers ok: false with refusedBy: "safety-gate", says why, and says what to do instead, including how to raise the mode.

batch, which runs many operations in one call, is gated too: every inner operation passes through the same check.

Changing the mode

  • At startup, with the environment variable MIF_BRIDGE_WRITE_MODE (read, scratch or full). Anything else falls back to scratch and logs a warning. To make it stick across launches on Windows, use setx MIF_BRIDGE_WRITE_MODE full and restart the editor.
  • While the editor runs, with the WRITE MODE control in the header of the MifBridge panel:
    • Lowering is always allowed. A person reaching for scratch mid-operation is trying to stop something.
    • Raising is refused while any bridge call is running. Some endpoints open progress dialogs that pump the UI, and a click could otherwise land on this control in the middle of the very call it would unlock. Try again once the bridge is idle.
    • Every raise is logged as a warning, so whoever reads the log afterwards can find the moment the gate opened.

No agent can reach it. The mode is not an endpoint, not a console variable and not a UI command. Nothing that takes a name can set it, so an agent cannot unlock its own gate.

File output stays inside the project

In scratch and read, a file an endpoint writes, such as an export, must land inside the project folder. An explicitly named path outside it is refused, and nothing is written. The default location under Saved/MifBridge is always allowed, which is where the Blender round trip already writes. full writes anywhere.

Engine content stays as shipped

From 1.2, in scratch and read, a call does not write under /Engine/ or into the content of a plugin the engine loaded, such as /Niagara/, /Water/ or a Fab plugin installed into the engine. A call that would make something new there is refused before it runs. A call that changed engine content anyway is answered as refused, and its undo record is kept, so undo_transactions takes it back. /Game/ and your project's own plugins stay writable, and full writes anywhere.

The change cap: a list before any mass change

From 1.2, one call that would change more than 25 things writes nothing. Things means distinct targets: actors, assets, objects, DataTable rows, and in Blender objects, materials and collections. Forty properties on one actor is one target, so normal work stays well under it.

  • A bulk write that can preview (batch, spawn_many, duplicate_actors, the DataTable writes, rename_asset and the other preview endpoints; in Blender bl_batch and bl_transform_objects) answers ok: false with capped: true, the full list and a previewId. Only a second call carrying that id applies the list, exactly as shown. If the world changed in between, the commit is refused.
  • A call that cannot be previewed is refused over the cap and says how to go on: split it into smaller calls, or ask the person to raise the cap. delete_asset with a folder deletes up to the cap in dependency order and answers what is left, so the same call continues.
  • Not capped: undo and redo, which replay your own history, and the escape hatches (run_console, run_python), whose effects cannot be read off a request.
  • Each project sets its own cap. mif_project_mode(change_cap=40) records it, or 0 turns it off, for the Unreal project (in MifBridgeProject.json beside the .uproject) or the open .blend. MIF_CHANGE_CAP in the editor's or Blender's environment sets the default for projects that have none. The cap is the person's choice: an agent raises it only when asked.
  • self_audit and bl_status report the cap in force as changeCap, with where it came from.

Knowing which real assets a call touched

This is detection, not prevention. Every response reports scratchClean. When a call modified a package outside the scratch area (/Game/_Mif), it is named in dirtiedRealPackages, with a scratchWarning.

In a gated mode nothing is saved, but a person pressing Ctrl+S afterwards would save it, and this is how you find out before that happens. A clean report is good evidence rather than proof, for three reasons:

  • An object modified twice in one frame is seen once.
  • The watch relies on the engine's modify notification.
  • Newly created assets are not reported: they are not yet anyone's work to lose.

Destructive operations need confirm

Operations that delete, rename or rewrite existing work require an explicit confirm: true, and refuse without it. Among them: deleting assets, actors, nodes, pins, variables, functions, components, interfaces, struct members and enum values; renaming assets, variables, functions and event dispatchers; writing or deleting DataTable rows; removing LODs, collision, physics bodies and animation notifies; consolidating assets, fixing redirectors, and clearing the undo history. describe_endpoint lists confirm among the parameters of every endpoint that takes it.

Undo

Transacted edits can be undone with Ctrl+Z in the editor. Saves, cooks, Play-In-Editor, compiles and console commands are not transactions, and undo cannot reverse them. That is one more reason they are gated.

Blender has no transactions, so the add-on records one undo step after every call that can change the scene, and undo, redo and undo_history walk those steps. Files written to disk and renders stay as they are. See MifBlender.

The crash-recovery prompt

After an editor crash, the next launch offers to restore auto-saved packages. That offer is a modal dialog, and while it is open the bridge cannot answer. The editor is up and the port is bound, but nothing responds.

MIF_BRIDGE_AUTO_DECLINE_RECOVERY=1 declines that prompt at startup. It discards real unsaved work, so it is meant for disposable automation projects only. It is off by default, and like the write mode, no endpoint or console variable can turn it on.