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,scratchorfull). Anything else falls back toscratchand logs a warning. To make it stick across launches on Windows, usesetx MIF_BRIDGE_WRITE_MODE fulland 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
scratchmid-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.
- Lowering is always allowed. A person reaching for
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_assetand the other preview endpoints; in Blenderbl_batchandbl_transform_objects) answersok: falsewithcapped: true, the full list and apreviewId. 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_assetwith 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, or0turns it off, for the Unreal project (inMifBridgeProject.jsonbeside the.uproject) or the open.blend.MIF_CHANGE_CAPin 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_auditandbl_statusreport the cap in force aschangeCap, 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.