diff --git a/core/src/main/java/info/openrocket/core/document/Simulation.java b/core/src/main/java/info/openrocket/core/document/Simulation.java
index c661ace65..a05125565 100644
--- a/core/src/main/java/info/openrocket/core/document/Simulation.java
+++ b/core/src/main/java/info/openrocket/core/document/Simulation.java
@@ -357,7 +357,7 @@ public class Simulation implements ChangeSource, Cloneable {
 	public boolean hasErrors() {
 		FlightData data = getSimulatedData();
 		for (int branchNo = 0; branchNo < data.getBranchCount(); branchNo++) {
-			if (data.getBranch(branchNo).getFirstEvent(FlightEvent.Type.SIM_ABORT) != null) {
+			if (hasErrors(branchNo)) {
 				return true;
 			}
 		}
diff --git a/core/src/main/java/info/openrocket/core/file/CSVExport.java b/core/src/main/java/info/openrocket/core/file/CSVExport.java
index 6fd26e23c..39bade149 100644
--- a/core/src/main/java/info/openrocket/core/file/CSVExport.java
+++ b/core/src/main/java/info/openrocket/core/file/CSVExport.java
@@ -259,8 +259,12 @@ public class CSVExport {
 
 	private static void printEvent(PrintWriter writer, FlightEvent e,
 			String commentStarter) {
-		writer.println(prependComment(commentStarter, "Event " + e.getType().name() +
+		writer.print(prependComment(commentStarter, "Event " + e.getType().name() +
 				" occurred at t=" + TextUtil.doubleToString(e.getTime()) + " seconds"));
+		if (e.getType() == FlightEvent.Type.SIM_WARN) {
+			writer.print(": " + (Warning) e.getData());
+		}
+		writer.println();
 	}
 
 	private static void writeSimulationComments(PrintWriter writer,
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java b/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java
index d8f8d2285..1891a92a0 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/OpenRocketSaver.java
@@ -430,7 +430,24 @@ public class OpenRocketSaver extends RocketSaver {
 			indent++;
 			
 			for (Warning w : data.getWarningSet()) {
-				writeElementWithAttribute("warning", "priority", w.getPriority().getExportLabel(), TextUtil.escapeXML(w.toString()));
+				writeln("<warning>");
+				indent++;
+
+				writeElement("id", w.getID().toString());
+				writeElement("description", w.getMessageDescription());
+				writeElement("priority", w.getPriority());
+
+				if (null != w.getSources()) {
+					for (RocketComponent c : w.getSources()) {
+						writeElement("source", c.getID());
+					}
+				}
+
+				// We write the whole string content for backwards compatibility with old versions
+				writeln(TextUtil.escapeXML(w.toString()));
+
+				indent--;
+				writeln("</warning>");
 			}
 			
 			// Check whether to store data
@@ -606,16 +623,21 @@ public class OpenRocketSaver extends RocketSaver {
 		// Write events
 		for (FlightEvent event : branch.getEvents()) {
 			String eventStr = "<event time=\"" + TextUtil.doubleToString(event.getTime())
-					+ "\" type=\"" + enumToXMLName(event.getType());
+					+ "\" type=\"" + enumToXMLName(event.getType()) + "\"";
+			
 			if (event.getSource() != null) {
-				eventStr += "\" source=\"" + TextUtil.escapeXML(event.getSource().getID());
+				eventStr += " source=\"" + TextUtil.escapeXML(event.getSource().getID()) + "\"";
 			}
 
+			if (event.getType() == FlightEvent.Type.SIM_WARN) {
+				eventStr += " id=\"" + TextUtil.escapeXML(((Warning) event.getData()).getID()) + "\"";
+			}
+			
 			if (event.getType() == FlightEvent.Type.SIM_ABORT) {
-				eventStr += "\" cause=\"" + enumToXMLName(((SimulationAbort)(event.getData())).getCause());
+				eventStr += " cause=\"" + enumToXMLName(((SimulationAbort)(event.getData())).getCause()) + "\"";
 			}
 
-			eventStr += "\"/>";
+			eventStr += "/>";
 			writeln(eventStr);
 		}
 		
@@ -676,14 +698,6 @@ public class OpenRocketSaver extends RocketSaver {
 			content = "";
 		writeln("<" + element + ">" + TextUtil.escapeXML(content) + "</" + element + ">");
 	}
-
-	private void writeElementWithAttribute(String element, String attributeName, String attribute, Object content) throws IOException {
-		content = content == null ? "" : content;
-
-		writeln("<" + element + " " + attributeName + " = \"" + attribute + "\">" + TextUtil.escapeXML(content) + "</" + element + ">");
-	}
-
-	
 	
 	private void writeln(String str) throws IOException {
 		if (str.length() == 0) {
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java
index 7ba4088b8..2b2277ce1 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataBranchHandler.java
@@ -3,6 +3,7 @@ package info.openrocket.core.file.openrocket.importt;
 import java.util.HashMap;
 import java.util.UUID;
 
+import info.openrocket.core.logging.Message;
 import info.openrocket.core.logging.SimulationAbort;
 import info.openrocket.core.logging.SimulationAbort.Cause;
 import info.openrocket.core.logging.WarningSet;
@@ -132,15 +133,14 @@ class FlightDataBranchHandler extends AbstractElementHandler {
 		if (element.equals("event")) {
 			double time;
 			FlightEvent.Type type;
-			SimulationAbort abort = null;
-			SimulationAbort.Cause cause = null;
+			Message data = null;
 			RocketComponent source = null;
 			String sourceID;
 
 			try {
 				time = DocumentConfig.stringToDouble(attributes.get("time"));
 			} catch (NumberFormatException e) {
-				warnings.add("Illegal event specification, ignoring.");
+				warnings.add("Illegal event time specification, ignoring: " + e.getMessage());
 				return;
 			}
 			
@@ -157,13 +157,23 @@ class FlightDataBranchHandler extends AbstractElementHandler {
 				source = rocket.findComponent(UUID.fromString(sourceID));
 			}
 
+			// For warning events, get the warning
+			if (type == FlightEvent.Type.SIM_WARN) {
+				data = simHandler.getWarningSet().findById(UUID.fromString(attributes.get("id")));
+			}
+			
 			// For aborts, get the cause
-			cause = (Cause) DocumentConfig.findEnum(attributes.get("cause"), SimulationAbort.Cause.class);
+			Cause cause = (Cause) DocumentConfig.findEnum(attributes.get("cause"), SimulationAbort.Cause.class);
 			if (cause != null) {
-				abort = new SimulationAbort(cause);
+				data = new SimulationAbort(cause);
 			}
 
-			branch.addEvent(new FlightEvent(type, time, source, abort));
+			try {
+				branch.addEvent(new FlightEvent(type, time, source, data));
+			} catch (Exception e) {
+				warnings.add("Illegal parameters for FlightEvent: " + e.getMessage());
+			}
+			
 			return;
 		}
 		
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java
index 6ce1573f1..1c6ad1b89 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/FlightDataHandler.java
@@ -39,7 +39,7 @@ class FlightDataHandler extends AbstractElementHandler {
 			WarningSet warnings) {
 		
 		if (element.equals("warning")) {
-			return PlainTextHandler.INSTANCE;
+			return new WarningHandler(context.getOpenRocketDocument().getRocket(), warningSet);
 		}
 		if (element.equals("databranch")) {
 			if (attributes.get("name") == null || attributes.get("types") == null) {
@@ -83,10 +83,10 @@ class FlightDataHandler extends AbstractElementHandler {
 			if (branch.getLength() > 0) {
 				branches.add(branch);
 			}
-		} else if (element.equals("warning")) {
-			String priorityStr = attributes.get("priority");
-			MessagePriority priority = MessagePriority.fromExportLabel(priorityStr);
-			warningSet.add(Warning.fromString(content, priority));
+			//		} else if (element.equals("warning")) {
+			//			String priorityStr = attributes.get("priority");
+			//			MessagePriority priority = MessagePriority.fromExportLabel(priorityStr);
+			//			warningSet.add(Warning.fromString(content, priority));
 		}
 	}
 	
@@ -157,6 +157,9 @@ class FlightDataHandler extends AbstractElementHandler {
 		data.getWarningSet().addAll(warningSet);
 		data.immute();
 	}
-	
-	
+
+
+	public WarningSet getWarningSet() {
+		return warningSet;
+	}
 }
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java
index f283144f1..80e062b03 100644
--- a/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/SingleSimulationHandler.java
@@ -156,6 +156,13 @@ class SingleSimulationHandler extends AbstractElementHandler {
 		doc.addSimulation(simulation);
 	}
 
+	/**
+	 * @return the warning set associated with this simulation
+	 */
+	public WarningSet getWarningSet() {
+		return dataHandler.getWarningSet();
+	}
+
 	private SimulationExtension compatibilityExtension(String className) {
 		JavaCode extension = Application.getInjector().getInstance(JavaCode.class);
 		extension.setClassName(className);
diff --git a/core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java b/core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java
new file mode 100644
index 000000000..edfde5bc5
--- /dev/null
+++ b/core/src/main/java/info/openrocket/core/file/openrocket/importt/WarningHandler.java
@@ -0,0 +1,73 @@
+package info.openrocket.core.file.openrocket.importt;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.UUID;
+
+import info.openrocket.core.file.simplesax.AbstractElementHandler;
+import info.openrocket.core.file.simplesax.ElementHandler;
+import info.openrocket.core.file.simplesax.PlainTextHandler;
+import info.openrocket.core.logging.MessagePriority;
+import info.openrocket.core.logging.Warning;
+import info.openrocket.core.logging.WarningSet;
+import info.openrocket.core.rocketcomponent.Rocket;
+import info.openrocket.core.rocketcomponent.RocketComponent;
+
+class WarningHandler extends AbstractElementHandler {
+	private Rocket rocket;
+	private WarningSet warningSet;
+	private Warning warning;
+	private UUID id = UUID.randomUUID();
+	private MessagePriority priority = MessagePriority.NORMAL;
+	private ArrayList<RocketComponent> sources = new ArrayList<>();
+	private String warningText;
+
+	public WarningHandler(Rocket rocket, WarningSet warningSet) {
+		this.rocket = rocket;
+		this.warningSet = warningSet;
+	}
+	
+	@Override
+	public ElementHandler openElement(String element, HashMap<String, String> attributes,
+									  WarningSet warnings) {
+		return PlainTextHandler.INSTANCE;
+	}
+
+	@Override
+	public void closeElement(String element, HashMap<String, String> attributes,
+							 String content, WarningSet warnings) {
+		if (element.equals("id")) {
+			id = UUID.fromString(content);
+		} else if (element.equals("description")) {
+			warning = Warning.fromString(content);
+		} else if (element.equals("priority")) {
+			priority = MessagePriority.fromExportLabel(content);
+		} else if (element.equals("source")) {
+			RocketComponent component = rocket.findComponent(UUID.fromString(content));
+			sources.add(component);
+		} else {
+			warnings.add("Unknown element '" + element + "', ignoring.");
+		}
+	}
+
+	@Override
+	public void endHandler(String element, HashMap<String, String> attributes,
+						   String content, WarningSet warnings) {
+
+		// If we didn't already create a warning, this came from an old version
+		if (null == warning) {
+			warning = Warning.fromString(content.trim());
+		}
+		if (null != id) {
+			warning.setID(id);
+		}
+		if (null != priority) {
+			warning.setPriority(priority);
+		}
+		if (null != sources) {
+			warning.setSources(sources.toArray(new RocketComponent[0]));
+		}
+
+		warningSet.add(warning);
+	}
+}
diff --git a/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java b/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java
index 3f4e13326..38bb894ec 100644
--- a/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java
+++ b/core/src/main/java/info/openrocket/core/file/simplesax/ElementHandler.java
@@ -80,4 +80,4 @@ public interface ElementHandler {
 	public abstract void endHandler(String element, HashMap<String, String> attributes,
 			String content, WarningSet warnings) throws SAXException;
 
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/info/openrocket/core/logging/Message.java b/core/src/main/java/info/openrocket/core/logging/Message.java
index 5f97fa01c..e7b9b46a1 100644
--- a/core/src/main/java/info/openrocket/core/logging/Message.java
+++ b/core/src/main/java/info/openrocket/core/logging/Message.java
@@ -1,17 +1,30 @@
 package info.openrocket.core.logging;
 
-import info.openrocket.core.rocketcomponent.RocketComponent;
 import java.util.Arrays;
+import java.util.UUID;
+
+import info.openrocket.core.rocketcomponent.RocketComponent;
 
 /**
  * Baseclass for logging messages (warnings, errors...)
  */
 public abstract class Message implements Cloneable {
+	/** Message ID **/
+	UUID id;
+	
 	/** The rocket component(s) that are the source of this message **/
 	private RocketComponent[] sources = null;
 
 	private MessagePriority priority = MessagePriority.NORMAL;
 
+	protected Message() {
+		this.id = UUID.randomUUID();
+	}
+
+	protected Message(UUID id) {
+		this.id = id;
+	}
+	
 	/**
 	 * Returns the message text + message source objects.
 	 * @return the message text + message source objects.
@@ -54,6 +67,20 @@ public abstract class Message implements Cloneable {
 	 */
 	public abstract boolean replaceBy(Message other);
 
+	/**
+	 * Return the ID
+	 */
+	 public UUID getID() {
+		 return id;
+	 }
+
+	/**
+	 * Set the ID
+	 **/
+	public void setID(UUID id) {
+		this.id = id;
+	}
+	
 	/**
 	 * Return the rocket component(s) that are the source of this warning.
 	 * @return the rocket component(s) that are the source of this warning. Returns null if no sources are specified.
diff --git a/core/src/main/java/info/openrocket/core/logging/MessageSet.java b/core/src/main/java/info/openrocket/core/logging/MessageSet.java
index be11b51d7..f71c8c039 100644
--- a/core/src/main/java/info/openrocket/core/logging/MessageSet.java
+++ b/core/src/main/java/info/openrocket/core/logging/MessageSet.java
@@ -1,5 +1,10 @@
 package info.openrocket.core.logging;
 
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
 import info.openrocket.core.rocketcomponent.RocketComponent;
 import info.openrocket.core.util.ArrayList;
 import info.openrocket.core.util.BugException;
@@ -7,10 +12,6 @@ import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.Monitorable;
 import info.openrocket.core.util.Mutable;
 
-import java.util.AbstractSet;
-import java.util.Iterator;
-import java.util.List;
-
 /**
  * A set that contains multiple <code>Message</code>s.  When adding a
  * {@link Message} to this set, the contents is checked for a message of the
@@ -69,7 +70,7 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> imple
      * @param sources the sources of the message (rocket components that caused the message)
      *
      */
-    public boolean add(E m, RocketComponent... sources) {
+	public boolean add(E m, RocketComponent... sources) {
         mutable.check();
         try {
             m = (E) m.clone();
@@ -78,7 +79,7 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> imple
         }
         m.setSources(sources);
         return add(m);
-    }
+	}
 
     /**
      * Add a <code>Message</code> of the specified type with the specified discriminator to the
@@ -149,6 +150,14 @@ public abstract class MessageSet<E extends Message> extends AbstractSet<E> imple
         return list;
     }
 
+	public Message findById(UUID id) {
+		for (Message m : messages) {
+			if (m.id.equals(id))
+				return m;
+		}
+		throw new BugException("Message with id " + id + " not found");
+	}
+
     public void immute() {
         mutable.immute();
     }
diff --git a/core/src/main/java/info/openrocket/core/logging/Warning.java b/core/src/main/java/info/openrocket/core/logging/Warning.java
index 30bd9364b..7361f7180 100644
--- a/core/src/main/java/info/openrocket/core/logging/Warning.java
+++ b/core/src/main/java/info/openrocket/core/logging/Warning.java
@@ -18,7 +18,8 @@ public abstract class Warning extends Message {
 	 * @return a Message with the specific text and priority.
 	 */
 	public static Warning fromString(String text, MessagePriority priority) {
-		return new Warning.Other(text, priority);
+		Warning.Other warn = new Warning.Other(text, priority);
+		return warn;
 	}
 
 	/**
@@ -27,8 +28,6 @@ public abstract class Warning extends Message {
 	public static Warning fromString(String text) {
 		return fromString(text, MessagePriority.NORMAL);
 	}
-
-
 	
 	/////////////  Specific warning classes  /////////////
 	
diff --git a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java
index 0e68c559f..97dd2b334 100644
--- a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java
+++ b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java
@@ -108,7 +108,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
 			
 			// No recovery device
 			if (!simulationConfig.hasRecoveryDevice()) {
-				currentStatus.getWarnings().add(Warning.NO_RECOVERY_DEVICE);
+				currentStatus.addWarning(Warning.NO_RECOVERY_DEVICE);
 			}
 			
 			currentStatus.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket()));
@@ -315,7 +315,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
 			if (currentStatus.isLanded() &&
 				(event.getType() != FlightEvent.Type.ALTITUDE) &&
 				(event.getType() != FlightEvent.Type.SIMULATION_END))
-				currentStatus.getWarnings().add(new Warning.EventAfterLanding(event));
+				currentStatus.addWarning(new Warning.EventAfterLanding(event));
 
 			// Check for motor ignition events, add ignition events to queue
 			for (MotorClusterState state : currentStatus.getActiveMotors() ){
@@ -500,12 +500,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
 							}
 						}
 						if (numActiveBelow != 1) {
-							currentStatus.getWarnings().add(Warning.SEPARATION_ORDER);
+							currentStatus.addWarning(Warning.SEPARATION_ORDER);
 						}
 
 					// If I haven't cleared the rail yet, flag a warning
 					if (!currentStatus.isLaunchRodCleared()) {
-						currentStatus.getWarnings().add(Warning.EARLY_SEPARATION);
+						currentStatus.addWarning(Warning.EARLY_SEPARATION);
 					}
 
 					// Create a new simulation branch for the booster
@@ -565,12 +565,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
 
 					// Check for launch rod
 					if (!currentStatus.isLaunchRodCleared()) {
-						currentStatus.getWarnings().add(Warning.RECOVERY_LAUNCH_ROD);
+						currentStatus.addWarning(Warning.RECOVERY_LAUNCH_ROD);
 					}
 
 					// Check current velocity
 					if (currentStatus.getRocketVelocity().length() > 20) {
-						currentStatus.getWarnings().add(new Warning.HighSpeedDeployment(currentStatus.getRocketVelocity().length()));
+						currentStatus.addWarning(new Warning.HighSpeedDeployment(currentStatus.getRocketVelocity().length()));
 					}
 
 					currentStatus.setLiftoff(true);
diff --git a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java
index 3e7f80b49..37b703f5a 100644
--- a/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java
+++ b/core/src/main/java/info/openrocket/core/simulation/FlightEvent.java
@@ -2,6 +2,7 @@ package info.openrocket.core.simulation;
 
 import info.openrocket.core.l10n.Translator;
 import info.openrocket.core.logging.SimulationAbort;
+import info.openrocket.core.logging.Warning;
 import info.openrocket.core.rocketcomponent.AxialStage;
 import info.openrocket.core.rocketcomponent.MotorMount;
 import info.openrocket.core.rocketcomponent.RocketComponent;
@@ -81,6 +82,11 @@ public class FlightEvent implements Comparable<FlightEvent> {
 		 */
 		TUMBLE(trans.get("FlightEvent.Type.TUMBLE")),
 
+		/**
+		 * A warning was raised during the execution of the simulation
+		 */
+		 SIM_WARN(trans.get("FlightEvent.Type.SIM_WARN")),
+
 		/**
 		 * It is impossible for the simulation proceed due to characteristics
 		 * of the rocket or flight configuration
@@ -118,8 +124,8 @@ public class FlightEvent implements Comparable<FlightEvent> {
 		this(type, time, source, null);
 	}
 	
-	public FlightEvent(final FlightEvent _sourceEvent, final RocketComponent _comp, final Object _data) {
-		this(_sourceEvent.type, _sourceEvent.time, _comp, _data);
+	public FlightEvent( final FlightEvent sourceEvent, final RocketComponent source, final Object data) {
+		this(sourceEvent.type, sourceEvent.time, source, data);
 	}
 	
 	public FlightEvent( final Type type, final double time, final RocketComponent source, final Object data) {
@@ -177,6 +183,13 @@ public class FlightEvent implements Comparable<FlightEvent> {
 		// finally, sort on event type
 		return this.type.ordinal() - o.type.ordinal();
 	}
+
+	public boolean equals(FlightEvent o) {
+		if ((this.type == Type.SIM_WARN) && (o.type == Type.SIM_WARN))
+			return ((Warning)(this.data)).equals((Warning)(o.data));
+
+		return this.equals(0);
+	}
 	
 	@Override
 	public String toString() {
@@ -241,6 +254,17 @@ public class FlightEvent implements Comparable<FlightEvent> {
 				}
 			}
 			break;
+		case SIM_WARN:
+			if (null != this.source) {
+				// rather than making event sources take sets of components, or trying to keep them
+				// in sync with the sources of Warnings, we'll require the event source to be null
+				// and pull the actual sources from the Warning
+				throw new IllegalStateException(type.name()+" event requires null source component; was " + this.source);
+			}	
+			if (( null == this.data ) || ( ! ( this.data instanceof Warning ))) {
+				throw new IllegalStateException(type.name()+" events require Warning objects");
+			}
+			break;
 		case SIM_ABORT:
 			if (( null == this.data ) || ( ! ( this.data instanceof SimulationAbort ))) {
 				throw new IllegalStateException(type.name()+" events require SimulationAbort objects");
diff --git a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java
index f94746090..b37733e48 100644
--- a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java
+++ b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java
@@ -401,7 +401,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
 		 * launch rod or 0.25 seconds after departure, and when the velocity has dropped
 		 * below 20% of the max. velocity.
 		 */
-		WarningSet warnings = status.getWarnings();
+		WarningSet warnings = new WarningSet();
 		store.maxZvelocity = MathUtil.max(store.maxZvelocity, status.getRocketVelocity().z);
 		
 		if (!status.isLaunchRodCleared()) {
@@ -421,7 +421,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
 		// Calculate aerodynamic forces
 		store.forces = status.getSimulationConditions().getAerodynamicCalculator()
 				.getAerodynamicForces(status.getConfiguration(), store.flightConditions, warnings);
-		
+		if (null != warnings) {
+			status.addWarnings(warnings);
+		}
 
 		// Add very small randomization to yaw & pitch moments to prevent over-perfect flight
 		// TODO: HIGH: This should rather be performed as a listener
diff --git a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java
index 4768bc3d5..54c279bfc 100644
--- a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java
+++ b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java
@@ -9,6 +9,7 @@ import java.util.Set;
 
 import info.openrocket.core.aerodynamics.FlightConditions;
 import info.openrocket.core.logging.SimulationAbort;
+import info.openrocket.core.logging.Warning;
 import info.openrocket.core.logging.WarningSet;
 import info.openrocket.core.motor.MotorConfiguration;
 import info.openrocket.core.motor.MotorConfigurationId;
@@ -418,6 +419,23 @@ public class SimulationStatus implements Cloneable, Monitorable {
 		this.warnings = warnings;
 	}
 
+	public void addWarning(Warning warning) {
+		if (null == warnings) {
+			setWarnings(new WarningSet());
+		}
+		if (!warnings.contains(warning)) {
+			log.trace("Add warning: \"" + warning + "\"");
+			getFlightDataBranch().addEvent(new FlightEvent(FlightEvent.Type.SIM_WARN, getSimulationTime(), null, warning));
+			warnings.add(warning);
+		}
+	}
+
+	public void addWarnings(WarningSet warnings) {
+		for (Warning warning : warnings) {
+			addWarning(warning);
+		}
+	}
+
 	public WarningSet getWarnings() {
 		return warnings;
 	}
diff --git a/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java b/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java
index e328d2871..ffd6119af 100644
--- a/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java
+++ b/core/src/main/java/info/openrocket/core/simulation/listeners/SimulationListenerHelper.java
@@ -637,7 +637,7 @@ public class SimulationListenerHelper {
 	private static void warn(SimulationStatus status, SimulationListener listener) {
 		if (!listener.isSystemListener()) {
 			log.info("Non-system listener " + listener + " affected the simulation");
-			status.getWarnings().add(Warning.LISTENERS_AFFECTED);
+			status.addWarning(Warning.LISTENERS_AFFECTED);
 		}
 	}
 }
diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties
index 272459b40..091c418a1 100644
--- a/core/src/main/resources/l10n/messages.properties
+++ b/core/src/main/resources/l10n/messages.properties
@@ -2141,6 +2141,7 @@ FlightEvent.Type.STAGE_SEPARATION = Stage separation
 FlightEvent.Type.APOGEE = Apogee
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment
 FlightEvent.Type.GROUND_HIT = Ground hit
+FlightEvent.Type.SIM_WARN = Warning
 FlightEvent.Type.SIM_ABORT = Simulation abort
 FlightEvent.Type.SIMULATION_END = Simulation end
 FlightEvent.Type.ALTITUDE = Altitude change
diff --git a/core/src/main/resources/l10n/messages_ar.properties b/core/src/main/resources/l10n/messages_ar.properties
index ca25e2b89..609c91090 100644
--- a/core/src/main/resources/l10n/messages_ar.properties
+++ b/core/src/main/resources/l10n/messages_ar.properties
@@ -1711,6 +1711,7 @@ FlightEvent.Type.STAGE_SEPARATION = فصل المرحلة
 FlightEvent.Type.APOGEE = الأوج
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = نشر جهاز الإسترداد
 FlightEvent.Type.GROUND_HIT = الإصطدام بالأرض
+FlightEvent.Type.SIM_WARN = تحذير
 FlightEvent.Type.SIMULATION_END = نهاية المحاكاة
 FlightEvent.Type.ALTITUDE = تغيير الإرتفاع
 FlightEvent.Type.TUMBLE = هبوط
diff --git a/core/src/main/resources/l10n/messages_de.properties b/core/src/main/resources/l10n/messages_de.properties
index 9499d643c..9544e29a4 100644
--- a/core/src/main/resources/l10n/messages_de.properties
+++ b/core/src/main/resources/l10n/messages_de.properties
@@ -1265,6 +1265,7 @@ FlightEvent.Type.STAGE_SEPARATION = Stufentrennung
 FlightEvent.Type.APOGEE = Apog�um
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Ausl�sung des Bergungssystems
 FlightEvent.Type.GROUND_HIT = Landung
+FlightEvent.Type.SIM_WARN = Warnung
 FlightEvent.Type.SIMULATION_END = Ende der Simulation
 FlightEvent.Type.ALTITUDE = H�hen�nderung
 
diff --git a/core/src/main/resources/l10n/messages_es.properties b/core/src/main/resources/l10n/messages_es.properties
index 61fedf408..154d5a816 100644
--- a/core/src/main/resources/l10n/messages_es.properties
+++ b/core/src/main/resources/l10n/messages_es.properties
@@ -350,6 +350,7 @@ FlightEvent.Type.LAUNCH                     = Lanzamiento
 FlightEvent.Type.LAUNCHROD                  = Abandono de la Gu\u00eda de lanzamiento
 FlightEvent.Type.LIFTOFF                    = Despegue
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Despliegue del sistema de recuperaci\u00f3n
+FlightEvent.Type.SIM_WARN                   = Peligro, cohete inestable
 FlightEvent.Type.SIMULATION_END             = Fin de la simulaci\u00f3n
 FlightEvent.Type.STAGE_SEPARATION           = Separaci\u00f3n de etapa
 FlightEvent.Type.TUMBLE                     = Volteo
diff --git a/core/src/main/resources/l10n/messages_fr.properties b/core/src/main/resources/l10n/messages_fr.properties
index 9b3e50229..98ca53516 100644
--- a/core/src/main/resources/l10n/messages_fr.properties
+++ b/core/src/main/resources/l10n/messages_fr.properties
@@ -341,6 +341,7 @@ FlightEvent.Type.LAUNCH                     = Lancement
 FlightEvent.Type.LAUNCHROD                  = D\u00E9gagement de rampe de lancement
 FlightEvent.Type.LIFTOFF                    = D\u00E9collage
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = D\u00E9ploiement du dispositif de r\u00E9cup\u00E9ration
+FlightEvent.Type.SIM_WARN                   = Avertissement
 FlightEvent.Type.SIMULATION_END             = Fin de la simulation
 FlightEvent.Type.STAGE_SEPARATION           = S\u00E9paration d'\u00E9tage
 FlightEvent.Type.TUMBLE                     = D\u00E9gringolade
diff --git a/core/src/main/resources/l10n/messages_it.properties b/core/src/main/resources/l10n/messages_it.properties
index 950a6e7ce..19f4f4715 100644
--- a/core/src/main/resources/l10n/messages_it.properties
+++ b/core/src/main/resources/l10n/messages_it.properties
@@ -1269,6 +1269,7 @@ FlightEvent.Type.STAGE_SEPARATION = Separazione degli stadi
 FlightEvent.Type.APOGEE = Apogeo
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = distacco del dispositivo di recupero
 FlightEvent.Type.GROUND_HIT = Atterraggio
+FlightEvent.Type,SIM_WARN = Avvisi
 FlightEvent.Type.SIMULATION_END = Fine simulazione
 FlightEvent.Type.ALTITUDE = Cambio altitudine
 
diff --git a/core/src/main/resources/l10n/messages_ja.properties b/core/src/main/resources/l10n/messages_ja.properties
index b1e5c1c7c..86e9a7789 100644
--- a/core/src/main/resources/l10n/messages_ja.properties
+++ b/core/src/main/resources/l10n/messages_ja.properties
@@ -1316,6 +1316,7 @@ FlightEvent.Type.STAGE_SEPARATION  = \u30B9\u30C6\u30FC\u30B8\u5206\u96E2
 FlightEvent.Type.APOGEE  = \u6700\u9AD8\u5230\u9054\u70B9
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT  = \u30EA\u30AB\u30D0\u30EA\u30FC\u88C5\u7F6E\u5C55\u958B
 FlightEvent.Type.GROUND_HIT  = \u7740\u5730
+FlightEvent.Type.SIM_WARN = \u30A8\u30E9\u30FC
 FlightEvent.Type.SIMULATION_END  = \u30B7\u30DF\u30E5\u30EC\u30FC\u30B7\u30E7\u30F3\u7D42\u4E86
 FlightEvent.Type.ALTITUDE  = \u59FF\u52E2\u5909\u66F4
 
diff --git a/core/src/main/resources/l10n/messages_nl.properties b/core/src/main/resources/l10n/messages_nl.properties
index 690af23a8..ecd1496c5 100644
--- a/core/src/main/resources/l10n/messages_nl.properties
+++ b/core/src/main/resources/l10n/messages_nl.properties
@@ -1634,6 +1634,7 @@ FlightEvent.Type.STAGE_SEPARATION = Trap afscheiding
 FlightEvent.Type.APOGEE = Apogee
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Terugvordering toestel uitrol
 FlightEvent.Type.GROUND_HIT = Grond geraakt
+FlightEvent.Type.SIM_WARN = Waarschuwing
 FlightEvent.Type.SIMULATION_END = Simulatie-einde
 FlightEvent.Type.ALTITUDE = Hoogteverandering
 FlightEvent.Type.TUMBLE = Tuimelen
diff --git a/core/src/main/resources/l10n/messages_pl.properties b/core/src/main/resources/l10n/messages_pl.properties
index 4428b15fd..d46226ee5 100644
--- a/core/src/main/resources/l10n/messages_pl.properties
+++ b/core/src/main/resources/l10n/messages_pl.properties
@@ -1210,6 +1210,7 @@ ComponentInfo.EngineBlock  = <b>Blokada silnika</b> unieruchamia silnik wewn\u01
  FlightEvent.Type.APOGEE  =  Apogeum
  FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT  =  Aktywacja uk\u0142adu odzysku
  FlightEvent.Type.GROUND_HIT  =  Uderzenie w ziemi\u0119
+ FlightEvent.Type.SIM_WARN = Ostrze\u017Cenie
  FlightEvent.Type.SIMULATION_END  =  Koniec symulacji
  FlightEvent.Type.ALTITUDE  =  Zmiana wysoko\u015Bci
  
diff --git a/core/src/main/resources/l10n/messages_pt.properties b/core/src/main/resources/l10n/messages_pt.properties
index 697c732d3..c71236ce9 100644
--- a/core/src/main/resources/l10n/messages_pt.properties
+++ b/core/src/main/resources/l10n/messages_pt.properties
@@ -330,6 +330,7 @@ FlightEvent.Type.LAUNCH                     = Lan\u00e7amento
 FlightEvent.Type.LAUNCHROD                  = Folga da haste de lan\u00e7amento
 FlightEvent.Type.LIFTOFF                    = Decolagem
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Implanta\u00e7\u00e3o de dispositivos de recupera\u00e7\u00e3o
+FlightEvent.Type.SIM_WARN                   = Alerta
 FlightEvent.Type.SIMULATION_END             = Final de simula\u00e7\u00e3o
 FlightEvent.Type.STAGE_SEPARATION           = Separa\u00e7\u00e3o do est\u00e1gio
 FlightEvent.Type.TUMBLE                     = Tumbling
diff --git a/core/src/main/resources/l10n/messages_ru.properties b/core/src/main/resources/l10n/messages_ru.properties
index 6bd2670b3..03e9250f1 100644
--- a/core/src/main/resources/l10n/messages_ru.properties
+++ b/core/src/main/resources/l10n/messages_ru.properties
@@ -1683,6 +1683,7 @@ FlightEvent.Type.STAGE_SEPARATION = \u0420\u0430\u0437\u0434\u0435\u043B\u0435\u
 FlightEvent.Type.APOGEE = \u0410\u043F\u043E\u0433\u0435\u0439
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = \u0421\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u043D\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0441\u043F\u0430\u0441\u0435\u043D\u0438\u044F
 FlightEvent.Type.GROUND_HIT = \u041F\u0440\u0438\u0437\u0435\u043C\u043B\u0435\u043D\u0438\u0435
+FlightEvent.Type.SIM_WARN = \u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435
 FlightEvent.Type.SIMULATION_END = \u041A\u043E\u043D\u0435\u0446 \u0440\u0430\u0441\u0447\u0435\u0442\u0430
 FlightEvent.Type.ALTITUDE = \u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u0432\u044B\u0441\u043E\u0442\u044B
 FlightEvent.Type.TUMBLE = \u041E\u043F\u0440\u043E\u043A\u0438\u0434\u044B\u0432\u0430\u043D\u0438\u0435
diff --git a/core/src/main/resources/l10n/messages_uk_UA.properties b/core/src/main/resources/l10n/messages_uk_UA.properties
index e3a6b2714..c3e5770ec 100644
--- a/core/src/main/resources/l10n/messages_uk_UA.properties
+++ b/core/src/main/resources/l10n/messages_uk_UA.properties
@@ -2007,6 +2007,7 @@ FlightEvent.Type.SIMULATION_END = \u0417\u0430\u043a\u0456\u043d\u0447\u0435\u04
 FlightEvent.Type.ALTITUDE = \u0417\u043c\u0456\u043d\u0430 \u0432\u0438\u0441\u043e\u0442\u0438
 FlightEvent.Type.TUMBLE = \u041f\u0435\u0440\u0435\u043a\u0438\u0434\u0430\u043d\u043d\u044f
 FlightEvent.Type.EXCEPTION = \u0412\u0438\u043d\u044f\u0442\u043e\u043a
+>>>>>>> unstable
 
 ! ThrustCurveMotorColumns
 TCurveMotorCol.MANUFACTURER = \u0412\u0438\u0440\u043e\u0431\u043d\u0438\u043a
diff --git a/core/src/main/resources/l10n/messages_zh_CN.properties b/core/src/main/resources/l10n/messages_zh_CN.properties
index 49efc0121..f9903be6f 100644
--- a/core/src/main/resources/l10n/messages_zh_CN.properties
+++ b/core/src/main/resources/l10n/messages_zh_CN.properties
@@ -367,6 +367,7 @@ FlightEvent.Type.LAUNCH                     = \u53D1\u5C04
 FlightEvent.Type.LAUNCHROD                  = \u79BB\u67B6
 FlightEvent.Type.LIFTOFF                    = \u8D77\u98DE
 FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = \u56DE\u6536\u88C5\u7F6E\u542F\u52A8
+FlightEvent.Type.SIM_WARN                   = \u8B66\u544A
 FlightEvent.Type.SIMULATION_END             = \u4EFF\u771F\u7ED3\u675F
 FlightEvent.Type.STAGE_SEPARATION           = \u7EA7\u95F4\u5206\u79BB
 FlightEvent.Type.TUMBLE                     = \u7FFB\u6EDA
diff --git a/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java b/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java
index fa6a893f3..3654f5eee 100644
--- a/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java
+++ b/core/src/test/java/info/openrocket/core/simulation/FlightEventsTest.java
@@ -1,6 +1,12 @@
 package info.openrocket.core.simulation;
 
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 import info.openrocket.core.document.Simulation;
+import info.openrocket.core.logging.Warning;
 import info.openrocket.core.rocketcomponent.AxialStage;
 import info.openrocket.core.rocketcomponent.BodyTube;
 import info.openrocket.core.rocketcomponent.FlightConfigurationId;
@@ -11,15 +17,12 @@ import info.openrocket.core.rocketcomponent.Rocket;
 import info.openrocket.core.simulation.exception.SimulationException;
 import info.openrocket.core.util.BaseTestCase;
 import info.openrocket.core.util.TestRockets;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;;
 
 /**
  * Tests to verify that simulations contain all the expected flight events.
  */
 public class FlightEventsTest extends BaseTestCase {
+	
 	private static final double EPSILON = 0.005;
 
 	/**
@@ -50,6 +53,7 @@ public class FlightEventsTest extends BaseTestCase {
 				new FlightEvent(FlightEvent.Type.LAUNCHROD, 0.13, null),
 				new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, motorMountTube),
 				new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, stage),
+				new FlightEvent(FlightEvent.Type.SIM_WARN, 2.0, null, new Warning.HighSpeedDeployment(80.6)),
 				new FlightEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, 2.001, parachute),
 				new FlightEvent(FlightEvent.Type.APOGEE, 2.48, rocket),
 				new FlightEvent(FlightEvent.Type.GROUND_HIT, 42.97, null),
@@ -99,6 +103,9 @@ public class FlightEventsTest extends BaseTestCase {
 		final ParallelStage sideBoosters = (ParallelStage) centerBoosterBody.getChild(1);
 		final BodyTube sideBoosterBodies = (BodyTube) sideBoosters.getChild(1);
 
+		Warning warn = Warning.OPEN_AIRFRAME_FORWARD;
+		warn.setSources(new BodyTube[]{centerBoosterBody});
+		
 		// events whose time is too variable to check are given a time of 1200
 		for (int b = 0; b < 2; b++) {
 			FlightEvent[] expectedEvents = switch (b) {
@@ -130,6 +137,7 @@ public class FlightEventsTest extends BaseTestCase {
 						new FlightEvent(FlightEvent.Type.BURNOUT, 2.01, centerBoosterBody),
 						new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.01, centerBooster),
 						new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.01, centerBooster),
+						new FlightEvent(FlightEvent.Type.SIM_WARN, 2.01, null, warn),
 						new FlightEvent(FlightEvent.Type.TUMBLE, 2.85, null),
 						new FlightEvent(FlightEvent.Type.APOGEE, 3.78, rocket),
 						new FlightEvent(FlightEvent.Type.GROUND_HIT, 9.0, null),
@@ -158,12 +166,9 @@ public class FlightEventsTest extends BaseTestCase {
 
 		FlightEvent[] actualEvents = sim.getSimulatedData().getBranch(branchNo).getEvents().toArray(new FlightEvent[0]);
 
-		// Test event count
-		assertEquals(expectedEvents.length, actualEvents.length, "Branch " + branchNo + " invalid number of events ");
-
 		// Test that all expected events are present, in the right order, at the right
 		// time, from the right sources
-		for (int i = 0; i < actualEvents.length; i++) {
+		for (int i = 0; i < Math.min(expectedEvents.length, actualEvents.length); i++) {
 			final FlightEvent expected = expectedEvents[i];
 			final FlightEvent actual = actualEvents[i];
 			assertSame(expected.getType(), actual.getType(),
@@ -185,6 +190,16 @@ public class FlightEventsTest extends BaseTestCase {
 			// Test that the event sources are correct
 			assertEquals(expected.getSource(), actual.getSource(),
 					"Branch " + branchNo + " FlightEvent " + i + " type " + expected.getType() + " has wrong source ");
+
+			// If it's a warning event, make sure the warning types match
+			if (expected.getType() == FlightEvent.Type.SIM_WARN) {
+				assertTrue(actual.getData() instanceof Warning, "SIM_WARN event data is not a Warning");
+				assertTrue(((Warning) expected.getData()).equals(actual.getData()), "Expected: " + expected.getData() + " but was: " + actual.getData());
+			}
 		}
+
+		// Test event count
+		assertEquals(expectedEvents.length, actualEvents.length, "Branch " + branchNo + " incorrect number of events ");
+		
 	}
 }
diff --git a/fileformat.txt b/fileformat.txt
index 144751d0b..f6503c444 100644
--- a/fileformat.txt
+++ b/fileformat.txt
@@ -71,3 +71,5 @@ The following file format versions exist:
       Added a priority attribute to simulation warnings.
       Added document preferences (<docprefs>).
       Added wind model settings (<wind mode="{average or multilevel}">), and windmodeltype to simulation conditions.
+      Added warning flight events
+      
diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java
index ba98485dd..5ae60db0b 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java
@@ -26,7 +26,7 @@ public class CAPlot extends Plot<CADataType, CADataBranch, CAPlotConfiguration>
 		// Create the series for each component
 		List<XYSeries> allSeries = new ArrayList<>();
 		for (int i = 0; i < components.size(); i++) {
-			XYSeries series = createSingleSeries(startIndex*1000 + i, type, unit, branch, branchIdx, branchName, baseName,
+			XYSeries series = createSingleSeries(startIndex*1000 + i, type, unit, branch, branchIdx, branchName, dataIndex, baseName,
 					components.get(i), componentNames.get(i));
 			allSeries.add(series);
 		}
@@ -35,10 +35,10 @@ public class CAPlot extends Plot<CADataType, CADataBranch, CAPlotConfiguration>
 	}
 
 	private XYSeries createSingleSeries(int key, CADataType type, Unit unit,
-										CADataBranch branch, int branchIdx, String branchName, String baseName,
+										CADataBranch branch, int branchIdx, String branchName, int dataIndex, String baseName,
 										RocketComponent component, String componentName) {
 		// Default implementation for regular DataBranch
-		MetadataXYSeries series = new MetadataXYSeries(key, false, true, branchIdx, unit.getUnit(),
+		MetadataXYSeries series = new MetadataXYSeries(key, false, true, branchIdx, dataIndex, unit.getUnit(),
 				branchName, baseName);
 
 		// Create a new description that includes the component name
diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java
index 06911c9f1..27005f4da 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/plot/EventGraphics.java
@@ -9,20 +9,27 @@ import java.util.Map;
 
 import javax.imageio.ImageIO;
 
+import info.openrocket.core.logging.MessagePriority;
+import info.openrocket.core.logging.Warning;
 import info.openrocket.core.simulation.FlightEvent;
 
 public class EventGraphics {
 
-	static Color getEventColor(FlightEvent.Type type) {
+	static Color getEventColor(FlightEvent event) {
+		FlightEvent.Type type = event.getType();
 		Color c = EVENT_COLORS.get(type);
 		if (c != null)
 			return c;
 		return DEFAULT_EVENT_COLOR;
 	}
 
-	static Image getEventImage(FlightEvent.Type type ) {
-		Image i = EVENT_IMAGES.get(type);
-		return i;
+	static Image getEventImage(FlightEvent event) {
+		FlightEvent.Type type = event.getType();
+		if (type == FlightEvent.Type.SIM_WARN) {
+			return MESSAGE_IMAGES.get(((Warning) event.getData()).getPriority());
+		} else {
+			return EVENT_IMAGES.get(type);
+		}
 	}
 	
 	private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
@@ -41,29 +48,38 @@ public class EventGraphics {
 		EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
 		EVENT_COLORS.put(FlightEvent.Type.TUMBLE, new Color(196, 0, 255));
 		EVENT_COLORS.put(FlightEvent.Type.EXCEPTION, new Color(255, 0, 0));
+		EVENT_COLORS.put(FlightEvent.Type.SIM_WARN, new Color(127, 127, 0));
 		EVENT_COLORS.put(FlightEvent.Type.SIM_ABORT, new Color(255, 0, 0));
 	}
 
 	private static final Map<FlightEvent.Type, Image> EVENT_IMAGES = new HashMap<>();
 	static {
-		loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
-		loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
-		loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
-		loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
-		loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
-		loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
-		loadImage(FlightEvent.Type.STAGE_SEPARATION,
+		loadImage(EVENT_IMAGES, FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.STAGE_SEPARATION,
 				"pix/eventicons/event-stage-separation.png");
-		loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
-		loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
+		loadImage(EVENT_IMAGES, FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
 				"pix/eventicons/event-recovery-device-deployment.png");
-		loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
-		loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
-		loadImage(FlightEvent.Type.EXCEPTION, "pix/eventicons/event-exception.png");
-		loadImage(FlightEvent.Type.SIM_ABORT, "pix/eventicons/event-exception.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.EXCEPTION, "pix/eventicons/event-exception.png");
+		loadImage(EVENT_IMAGES, FlightEvent.Type.SIM_ABORT, "pix/eventicons/event-exception.png");
+	}
+	
+	// Messages can happen at several priority levels, requiring different icons
+	private static final Map<MessagePriority, Image> MESSAGE_IMAGES = new HashMap<MessagePriority, Image>();
+	static {
+		loadImage(MESSAGE_IMAGES, MessagePriority.LOW, "pix/icons/warning_low.png");
+		loadImage(MESSAGE_IMAGES, MessagePriority.NORMAL, "pix/icons/warning_normal.png");
+		loadImage(MESSAGE_IMAGES, MessagePriority.HIGH, "pix/icons/warning_high.png");
 	}
 
-	private static void loadImage(FlightEvent.Type type, String file) {
+	private static <KeyType> void loadImage(Map<KeyType, Image> imageMap, KeyType type, String file) {
 		InputStream is;
 
 		is = ClassLoader.getSystemResourceAsStream(file);
@@ -74,10 +90,9 @@ public class EventGraphics {
 
 		try {
 			Image image = ImageIO.read(is);
-			EVENT_IMAGES.put(type, image);
+			imageMap.put(type, image);
 		} catch (IOException ignore) {
 			ignore.printStackTrace();
 		}
 	}
-
 }
diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java
index ce9c532c3..7c24334aa 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/plot/Plot.java
@@ -1,8 +1,11 @@
 package info.openrocket.swing.gui.plot;
 
 import info.openrocket.core.l10n.Translator;
+import info.openrocket.core.logging.Warning;
 import info.openrocket.core.simulation.DataBranch;
 import info.openrocket.core.simulation.DataType;
+import info.openrocket.core.simulation.FlightDataType;
+import info.openrocket.core.simulation.FlightEvent;
 import info.openrocket.core.startup.Application;
 import info.openrocket.core.unit.Unit;
 import info.openrocket.core.unit.UnitGroup;
@@ -52,6 +55,7 @@ import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /*
  * TODO: It should be possible to simplify this code quite a bit by using a single Renderer instance for
@@ -69,7 +73,7 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 	protected final List<ModifiedXYItemRenderer> renderers = new ArrayList<>();
 	protected final LegendItems legendItems;
 	protected final XYSeriesCollection[] data;
-	protected final C filledConfig;		// Configuration after using 'fillAutoAxes'
+	protected final C filledConfig;		// Configuration after using 'fillAutoAxes' and 'fitAxes'
 
 	protected final JFreeChart chart;
 
@@ -101,8 +105,8 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 
 		// Create the data series for both axes
 		this.data = new XYSeriesCollection[2];
-		this.data[0] = new XYSeriesCollection();
-		this.data[1] = new XYSeriesCollection();
+		this.data[Util.PlotAxisSelection.LEFT.getValue()] = new XYSeriesCollection();
+		this.data[Util.PlotAxisSelection.RIGHT.getValue()] = new XYSeriesCollection();
 
 		// Fill the auto-selections based on first branch selected.
 		this.filledConfig = config.fillAutoAxes(mainBranch);
@@ -120,9 +124,8 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 		int seriesCount = 0;
 
 		// Compute the axes based on the min and max value of all branches
-		C plotConfig = filledConfig.cloneConfiguration();
-		plotConfig.fitAxes(allBranches);
-		List<Axis> minMaxAxes = plotConfig.getAllAxes();
+		filledConfig.fitAxes(allBranches);
+		List<Axis> minMaxAxes = filledConfig.getAllAxes();
 
 		// Create the XYSeries objects from the flight data and store into the collections
 		String[] axisLabel = new String[2];
@@ -197,21 +200,40 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 				StandardXYToolTipGenerator tooltipGenerator = new StandardXYToolTipGenerator() {
 					@Override
 					public String generateToolTip(XYDataset dataset, int series, int item) {
+
 						XYSeriesCollection collection = data[finalAxisno];
 						if (collection.getSeriesCount() == 0) {
 							return null;
 						}
 						MetadataXYSeries ser = (MetadataXYSeries) collection.getSeries(series);
-						String unitY = ser.getUnit();
-						String unitX = domainUnit.getUnit();
-
-						double dataY = dataset.getYValue(series, item);
-						double dataX = dataset.getXValue(series, item);
 
 						// Determine the appropriate name based on the time and series
 						String name = getNameBasedOnIdxAndSeries(ser, item);
 
-						return formatSampleTooltip(name, dataX, unitX, dataY, unitY, item);
+						int dataTypeIdx = ser.getDataIdx();
+						DataType type = config.getType(dataTypeIdx);
+
+						String nameT = FlightDataType.TYPE_TIME.getName();
+						double dataT = Double.NaN;
+						List<Double> time = allBranches.get(ser.getBranchIdx()).get((T)FlightDataType.TYPE_TIME);
+						if (null != time) {
+							dataT = time.get(item);
+						}
+						String unitT = FlightDataType.TYPE_TIME.getUnitGroup().getDefaultUnit().toString();
+
+						String nameX = config.getDomainAxisType().getName();
+						double dataX = dataset.getXValue(series, item);
+						String unitX = domainUnit.getUnit();
+
+						String nameY = type.toString();
+						double dataY = dataset.getYValue(series, item);
+						String unitY = ser.getUnit();
+						
+						return formatTooltip(name,
+											 nameT, dataT, unitT,
+											 nameX, dataX, unitX,
+											 nameY, dataY, unitY,
+											 null);
 					}
 				};
 
@@ -278,7 +300,7 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 	protected List<XYSeries> createSeriesForType(int dataIndex, int startIndex, T type, Unit unit, B branch,
 												 int branchIdx, String branchName, String baseName) {
 		// Default implementation for regular DataBranch
-		MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, unit.getUnit(), branchName, baseName);
+		MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, dataIndex, unit.getUnit(), branchName, baseName);
 
 		List<Double> plotx = branch.get(filledConfig.getDomainAxisType());
 		List<Double> ploty = branch.get(type);
@@ -297,44 +319,65 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 		return type;
 	}
 
-	protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY,
-										 int sampleIdx, boolean addYValue) {
-		String ord_end = getOrdinalEnding(sampleIdx);
+	protected String formatTooltip(String dataName,
+								   String nameT, double dataT, String unitT,
+								   String nameX, double dataX, String unitX,
+								   String nameY, double dataY, String unitY,
+								   Set<FlightEvent> events) {
 
-		DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false);
+		final String strFormat = "%s: %s %s<br>";
+		
 		DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false);
 
 		StringBuilder sb = new StringBuilder();
-		sb.append(String.format("<html>" +
-				"<b><i>%s</i></b><br>", dataName));
+		sb.append("<html>");
 
-		if (addYValue) {
-			sb.append(String.format("Y: %s %s<br>", df_y.format(dataY), unitY));
+		sb.append(String.format("<b><i>%s</i></b><br>", dataName));
+
+		// Any events?
+		if ((null != events) && (events.size() != 0)) {
+			// Pass through and collect any warnings
+			for (FlightEvent event : events) {
+				if (event.getType() == FlightEvent.Type.SIM_WARN) {
+					sb.append("<b><i>Warning:  " + ((Warning) event.getData()).toString() + "</b></i><br>");
+				}
+			}
+
+			// Now pass through and collect the other events
+			String eventStr = "";
+			for (FlightEvent event : events) {
+				if (event.getType() != FlightEvent.Type.SIM_WARN) {
+					if (eventStr != "") {
+						eventStr = eventStr + ", ";
+					}
+					eventStr = eventStr + event.getType();
+				}
+			}
+			sb.append(eventStr + "<br>");
 		}
 
-		sb.append(String.format("X: %s %s<br>" +
-				"%d<sup>%s</sup> sample" +
-				"</html>", df_x.format(dataX), unitX, sampleIdx, ord_end));
+		// Valid Y data?
+		if (!Double.isNaN(dataY)) {
+			DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false);
+			sb.append(String.format(strFormat, nameY, df_y.format(dataY), unitY));
+		}
 
+		// Assuming X data is valid
+		sb.append(String.format(strFormat, nameX, df_x.format(dataX), unitX));
+
+		// If I've got time data, and my domain isn't time, add time to tooltip
+		if (!Double.isNaN(dataT) && !nameX.equals(nameT)) {
+			DecimalFormat df_t = DecimalFormatter.df(dataT, 2, false);
+			sb.append(String.format(strFormat, nameT, df_t.format(dataT), unitT));
+		}
+			
+		sb.append("</html>");
+		
 		return sb.toString();
 	}
 
-	protected String formatSampleTooltip(String dataName, double dataX, String unitX, double dataY, String unitY, int sampleIdx) {
-		return formatSampleTooltip(dataName, dataX, unitX, dataY, unitY, sampleIdx, true);
-	}
-
-	protected String formatSampleTooltip(String dataName, double dataX, String unitX, int sampleIdx) {
-		return formatSampleTooltip(dataName, dataX, unitX, 0, "", sampleIdx, false);
-	}
-
-	private String getOrdinalEnding(int n) {
-		if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) return "th";
-		return switch (n % 10) {
-			case 1 -> "st";
-			case 2 -> "nd";
-			case 3 -> "rd";
-			default -> "th";
-		};
+	protected String formatTooltip(String dataName, String nameX, double dataX, String unitX) {
+		return formatTooltip(dataName, "", Double.NaN, "", nameX, dataX, unitX, "", Double.NaN, "", null);
 	}
 
 	protected static class LegendItems implements LegendItemSource {
@@ -540,20 +583,27 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 
 	protected static class MetadataXYSeries extends XYSeries {
 		private final int branchIdx;
+		private final int dataIdx;
 		private final String unit;
 		private final String branchName;
 		private String baseName;
 
-		public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, String unit,
+		public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, int dataIdx, String unit,
 								String branchName, String baseName) {
 			super(key, autoSort, allowDuplicateXValues);
 			this.branchIdx = branchIdx;
+			this.dataIdx = dataIdx;
 			this.unit = unit;
 			this.branchName = branchName;
 			this.baseName = baseName;
 			updateDescription();
 		}
 
+		public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, String unit,
+								String branchName, String baseName) {
+			this(key, autoSort, allowDuplicateXValues, branchIdx, -1, unit, branchName, baseName);
+		}
+
 		public String getUnit() {
 			return unit;
 		}
@@ -562,6 +612,10 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
 			return branchIdx;
 		}
 
+		public int getDataIdx() {
+			return dataIdx;
+		}
+
 		public String getBranchName() {
 			return branchName;
 		}
diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java
index 40ff38540..5a9a38031 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlot.java
@@ -5,6 +5,7 @@ import java.awt.Font;
 import java.awt.Image;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.text.DecimalFormat;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -13,11 +14,13 @@ import java.util.Set;
 
 import info.openrocket.core.document.Simulation;
 import info.openrocket.core.logging.SimulationAbort;
+import info.openrocket.core.logging.Warning;
 import info.openrocket.core.simulation.FlightDataBranch;
 import info.openrocket.core.simulation.FlightDataType;
 import info.openrocket.core.simulation.FlightEvent;
 import info.openrocket.core.preferences.ApplicationPreferences;
 import info.openrocket.core.util.LinearInterpolator;
+import info.openrocket.swing.utils.DecimalFormatter;
 
 import org.jfree.chart.annotations.XYImageAnnotation;
 import org.jfree.chart.annotations.XYTitleAnnotation;
@@ -31,6 +34,8 @@ import org.jfree.chart.ui.VerticalAlignment;
 import org.jfree.chart.ui.RectangleAnchor;
 import org.jfree.chart.ui.RectangleEdge;
 import org.jfree.chart.ui.RectangleInsets;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
 
 @SuppressWarnings("serial")
 public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, SimulationPlotConfiguration> {
@@ -106,32 +111,31 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
 		plot.clearAnnotations();
 
 		// Store flight event information
-		List<Double> eventTimes = new ArrayList<>();
-		List<String> eventLabels = new ArrayList<>();
 		List<Color> eventColors = new ArrayList<>();
 		List<Image> eventImages = new ArrayList<>();
+		List<Set<FlightEvent>> eventSets = new ArrayList<>();
 
 		// Plot the markers
 		if (config.getDomainAxisType() == FlightDataType.TYPE_TIME && !preferences.getBoolean(ApplicationPreferences.MARKER_STYLE_ICON, false)) {
-			fillEventLists(branch, eventTimes, eventLabels, eventColors, eventImages);
-			plotVerticalLineMarkers(plot, eventTimes, eventLabels, eventColors);
+			fillEventLists(branch, eventColors, eventImages, eventSets);
+			plotVerticalLineMarkers(plot, eventColors, eventSets);
 
 		} else {	// Other domains are plotted as image annotations
 			if (branch == -1) {
 				// For icon markers, we need to do the plotting separately, otherwise you can have icon markers from e.g.
-				// branch 1 be plotted on branch 0
-				for (int b = 0; b < simulation.getSimulatedData().getBranchCount(); b++) {
-					fillEventLists(b, eventTimes, eventLabels, eventColors, eventImages);
+				// branch 1 be plotted on branch 0.  Also, need to take the branches in reverse order so lower number
+				// branches get their icons put on top of higher number and the tooltip headings are correct.
+				for (int b = simulation.getSimulatedData().getBranchCount() - 1; b >= 0; b--) {
+					fillEventLists(b, eventColors, eventImages, eventSets);
 					dataBranch = simulation.getSimulatedData().getBranch(b);
-					plotIconMarkers(plot, dataBranch, eventTimes, eventLabels, eventImages);
-					eventTimes.clear();
-					eventLabels.clear();
+					plotIconMarkers(plot, simulation, b, eventImages, eventSets);
+					eventSets = new ArrayList<>();
 					eventColors.clear();
 					eventImages.clear();
 				}
 			} else {
-				fillEventLists(branch, eventTimes, eventLabels, eventColors, eventImages);
-				plotIconMarkers(plot, dataBranch, eventTimes, eventLabels, eventImages);
+				fillEventLists(branch, eventColors, eventImages, eventSets);
+				plotIconMarkers(plot, simulation, branch, eventImages, eventSets);
 			}
 		}
 	}
@@ -154,78 +158,92 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
 			double separationTime = allBranches.get(i).getSeparationTime();
 			int separationIdx = allBranches.get(i).getDataIndexOfTime(separationTime);
 
-			// If the separation time is after the current data index, the stage is still attached, so add the
+			// If the separation time is at or after the current data index, the stage is still attached, so add the
 			// stage name to the series name
-			if (separationIdx != -1 && separationIdx > dataIdx) {
+			if (separationIdx != -1 && separationIdx >= dataIdx) {
 				newBranchName.append(" + ").append(allBranches.get(i).getName());
 			}
 		}
-
 		return newBranchName + ": " + ser.getBaseName();
 	}
 
-	private void fillEventLists(int branch, List<Double> eventTimes, List<String> eventLabels,
-								List<Color> eventColors, List<Image> eventImages) {
+	private void fillEventLists(int branch,
+								List<Color> eventColors, List<Image> eventImages, List<Set<FlightEvent>> eventSets) {
+		Set<FlightEvent> eventSet = new HashSet<>();
 		Set<FlightEvent.Type> typeSet = new HashSet<>();
 		double prevTime = -100;
-		String text = null;
 		Color color = null;
 		Image image = null;
 		int maxOrdinal = -1;
+		
 		for (EventDisplayInfo info : eventList) {
 			if (branch >= 0 && branch != info.stage) {
 				continue;
 			}
 
 			double t = info.time;
-			FlightEvent.Type type = info.event.getType();
+			FlightEvent event = info.event;
+			FlightEvent.Type type = event.getType();
+			
 			if (Math.abs(t - prevTime) <= 0.05) {
 				if (!typeSet.contains(type)) {
-					text = text + ", " + type.toString();
 					if (type.ordinal() > maxOrdinal) {
-						color = EventGraphics.getEventColor(type);
-						image = EventGraphics.getEventImage(type);
+						color = EventGraphics.getEventColor(event);
+						image = EventGraphics.getEventImage(event);
 						maxOrdinal = type.ordinal();
 					}
 					typeSet.add(type);
+					eventSet.add(event);
 				}
 
 			} else {
-				if (text != null) {
-					eventTimes.add(prevTime);
-					eventLabels.add(text);
+				if (!eventSet.isEmpty()) {
 					eventColors.add(color);
 					eventImages.add(image);
+					eventSets.add(eventSet);
 				}
 				prevTime = t;
-				text = type.toString();
-				color = EventGraphics.getEventColor(type);
-				image = EventGraphics.getEventImage(type);
+				color = EventGraphics.getEventColor(event);
+				image = EventGraphics.getEventImage(event);
 				typeSet.clear();
 				typeSet.add(type);
+				eventSet = new HashSet<>();
+				eventSet.add(event);
 				maxOrdinal = type.ordinal();
 			}
-
 		}
-		if (text != null) {
-			eventTimes.add(prevTime);
-			eventLabels.add(text);
+		if (!eventSet.isEmpty()) {
 			eventColors.add(color);
 			eventImages.add(image);
+			eventSets.add(eventSet);
 		}
 	}
 
-	private static void plotVerticalLineMarkers(XYPlot plot, List<Double> eventTimes, List<String> eventLabels, List<Color> eventColors) {
+	private static String constructEventLabels(Set<FlightEvent> events) {
+		String text = "";
+
+		for (FlightEvent event : events) {
+			if (text != "") {
+				text += ", ";
+			}
+			text += event.getType().toString();
+		}
+
+		return text;
+	}
+
+	private static void plotVerticalLineMarkers(XYPlot plot, List<Color> eventColors, List<Set<FlightEvent>> eventSets) {
 		double markerWidth = 0.01 * plot.getDomainAxis().getUpperBound();
 
 		// Domain time is plotted as vertical lines
-		for (int i = 0; i < eventTimes.size(); i++) {
-			double t = eventTimes.get(i);
-			String event = eventLabels.get(i);
+		for (int i = 0; i < eventSets.size(); i++) {
+			Set<FlightEvent> events = eventSets.get(i);
+			double t = ((FlightEvent)events.toArray()[0]).getTime();
+			String eventLabel = constructEventLabels(events);
 			Color color = eventColors.get(i);
 
 			ValueMarker m = new ValueMarker(t);
-			m.setLabel(event);
+			m.setLabel(eventLabel);
 			m.setPaint(color);
 			m.setLabelPaint(color);
 			m.setAlpha(0.7f);
@@ -238,53 +256,85 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
 		}
 	}
 
-	private void plotIconMarkers(XYPlot plot, FlightDataBranch dataBranch, List<Double> eventTimes,
-								List<String> eventLabels, List<Image> eventImages) {
+	private void plotIconMarkers(XYPlot plot, Simulation simulation, int branch, List<Image> eventImages, List<Set<FlightEvent>> eventSets) {
+
+		FlightDataBranch dataBranch = simulation.getSimulatedData().getBranch(branch);
+
 		List<Double> time = dataBranch.get(FlightDataType.TYPE_TIME);
+		String tName = FlightDataType.TYPE_TIME.getName();
 		List<Double> domain = dataBranch.get(config.getDomainAxisType());
-
 		LinearInterpolator domainInterpolator = new LinearInterpolator(time, domain);
+		String xName = config.getDomainAxisType().getName();
 
-		for (int i = 0; i < eventTimes.size(); i++) {
-			double t = eventTimes.get(i);
-			Image image = eventImages.get(i);
+		List<Axis> minMaxAxes = filledConfig.getAllAxes();
 
-			if (image == null) {
-				continue;
-			}
+		for (int axisno = 0; axisno < data.length; axisno++) {
+			// Image annotations are drawn using the data space defined by the left axis, so
+			// the position of annotations on the right axis need to be mapped to the left axis
+			// dataspace.
+			
+			double minLeft = minMaxAxes.get(0).getMinValue();
+			double maxLeft = minMaxAxes.get(0).getMaxValue();
+			
+			double minThis = minMaxAxes.get(axisno).getMinValue();
+			double maxThis = minMaxAxes.get(axisno).getMaxValue();
+			
+			double slope = (maxLeft - minLeft)/(maxThis - minThis);
+			double intercept = (maxThis * minLeft - maxLeft * minThis)/(maxThis - minThis);
+			
+			XYSeriesCollection collection = data[axisno];
+			for (MetadataXYSeries series : (List<MetadataXYSeries>)(collection.getSeries())) {
 
-			double xcoord = domainInterpolator.getValue(t);
-
-			for (int index = 0; index < config.getDataCount(); index++) {
-				FlightDataType type = config.getType(index);
-				List<Double> range = dataBranch.get(type);
-
-				LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range);
-				// Image annotations are not supported on the right-side axis
-				// TODO: LOW: Can this be achieved by JFreeChart?
-				if (filledConfig.getAxis(index) != Util.PlotAxisSelection.LEFT.getValue()) {
+				if (series.getBranchIdx() != branch) {
 					continue;
 				}
 
-				double ycoord = rangeInterpolator.getValue(t);
-				if (!Double.isNaN(ycoord)) {
-					// Convert units
+				int dataTypeIdx = series.getDataIdx();
+				FlightDataType type = config.getType(dataTypeIdx);
+				String yName = type.toString();
+				List<Double> range = dataBranch.get(type);
+				LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range);
+				
+				for (int i = 0; i < eventSets.size(); i++) {
+					Set<FlightEvent> events = eventSets.get(i);
+					double t = ((FlightEvent)events.toArray()[0]).getTime();
+					Image image = eventImages.get(i);
+					if (image == null) {
+						continue;
+					}
+
+					double xcoord = domainInterpolator.getValue(t);
+					double ycoord = rangeInterpolator.getValue(t);
+				
 					xcoord = config.getDomainAxisUnit().toUnit(xcoord);
-					ycoord = config.getUnit(index).toUnit(ycoord);
-					
-					// Get the sample index of the flight event. Because this can be an interpolation between two samples,
-					// take the closest sample.
-					final int sampleIdx;
-					Optional<Double> closestSample = time.stream()
-						.min(Comparator.comparingDouble(sample -> Math.abs(sample - t)));
-					sampleIdx = closestSample.map(time::indexOf).orElse(-1);
-					
-					String tooltipText = formatSampleTooltip(eventLabels.get(i), xcoord, config.getDomainAxisUnit().getUnit(), sampleIdx) ;
-					
-					XYImageAnnotation annotation =
-						new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
-					annotation.setToolTipText(tooltipText);
-					plot.addAnnotation(annotation);
+					ycoord = config.getUnit(dataTypeIdx).toUnit(ycoord);
+				
+					if (!Double.isNaN(ycoord)) {
+						// Get the sample index of the flight event. Because this can be an interpolation between two samples,
+						// take the closest sample.
+						final int sampleIdx;
+						Optional<Double> closestSample = time.stream()
+							.min(Comparator.comparingDouble(sample -> Math.abs(sample - t)));
+						sampleIdx = closestSample.map(time::indexOf).orElse(-1);
+						
+						// Convert units
+						String unitX = config.getDomainAxisUnit().getUnit();
+						String unitY = series.getUnit();
+						String unitT = FlightDataType.TYPE_TIME.getUnitGroup().getDefaultUnit().toString();
+						String tooltipText = formatTooltip(getNameBasedOnIdxAndSeries(series, sampleIdx),
+														   tName, t, unitT,
+														   xName, xcoord, unitX,
+														   yName, ycoord, unitY,
+														   events);
+						double yloc = slope * ycoord + intercept;
+						
+						if (!Double.isNaN(xcoord) && !Double.isNaN(ycoord)) {
+							XYImageAnnotation annotation =
+								new XYImageAnnotation(xcoord, yloc, image, RectangleAnchor.CENTER);
+							annotation.setToolTipText(tooltipText);
+							plot.addAnnotation(annotation);
+						}
+					}
 				}
 			}
 		}
diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java
index 258ad08cf..cd6abbde2 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/plot/SimulationPlotConfiguration.java
@@ -32,6 +32,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -48,6 +49,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -62,6 +64,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -76,6 +79,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -91,6 +95,8 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
+		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -102,6 +108,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.addPlotDataType(FlightDataType.TYPE_BASE_DRAG_COEFF, 0);
 		config.addPlotDataType(FlightDataType.TYPE_PRESSURE_DRAG_COEFF, 0);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -120,6 +127,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -136,6 +144,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
@@ -151,6 +160,7 @@ public class SimulationPlotConfiguration extends PlotConfiguration<FlightDataTyp
 		config.setEvent(FlightEvent.Type.GROUND_HIT, true);
 		config.setEvent(FlightEvent.Type.TUMBLE, true);
 		config.setEvent(FlightEvent.Type.EXCEPTION, true);
+		config.setEvent(FlightEvent.Type.SIM_WARN, true);
 		config.setEvent(FlightEvent.Type.SIM_ABORT, true);
 		configs.add(config);
 
diff --git a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java
index b1034e9c8..0950bfef9 100644
--- a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java
+++ b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationPlotPanel.java
@@ -174,8 +174,7 @@ public class SimulationPlotPanel extends PlotPanel<FlightDataType, FlightDataBra
 		col0.setPreferredWidth(w);
 		col0.setMaxWidth(w);
 		table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
-		selectorPanel.add(new JScrollPane(table), "height 200px, width 200lp, grow 1, wrap rel");
-
+		selectorPanel.add(new JScrollPane(table), "width 200lp, grow 1, wrap rel");
 
 		////  All + None buttons
 		JButton button = new JButton(trans.get("simplotpanel.but.All"));