diff --git a/assignments/src/MV3500Cohort2019JulySeptember/homework4/Brennenstuhl/BrennenstuhlEspduSender.java b/assignments/src/MV3500Cohort2019JulySeptember/homework4/Brennenstuhl/BrennenstuhlEspduSender.java
index d05532b965828d7f679a967031ef7baad2c15b08..86607d9a4eb2768d66eb0e41826e5f4bffadee89 100644
--- a/assignments/src/MV3500Cohort2019JulySeptember/homework4/Brennenstuhl/BrennenstuhlEspduSender.java
+++ b/assignments/src/MV3500Cohort2019JulySeptember/homework4/Brennenstuhl/BrennenstuhlEspduSender.java
@@ -275,7 +275,7 @@ public class BrennenstuhlEspduSender
 
 				FirePdu firePdu = new FirePdu();
                 firePdu.setLocationInWorldCoordinates(espdu.getEntityLocation());
-				byte[] fireArray = firePdu.marshal();
+				byte[] fireArray = firePdu.marshal().array();
 
 				broadcastAddresses = getBroadcastAddresses();
 				Iterator iterator = broadcastAddresses.iterator();
diff --git a/assignments/src/MV3500Cohort2019JulySeptember/homework4/Schutt/SchuttESPDUSender.java b/assignments/src/MV3500Cohort2019JulySeptember/homework4/Schutt/SchuttESPDUSender.java
index 4b6c630e24760b8ad271a9a52c468d21beceea7f..220daafdbb802b40371f15b25bc70b0ae8f215d2 100644
--- a/assignments/src/MV3500Cohort2019JulySeptember/homework4/Schutt/SchuttESPDUSender.java
+++ b/assignments/src/MV3500Cohort2019JulySeptember/homework4/Schutt/SchuttESPDUSender.java
@@ -287,7 +287,7 @@ public class SchuttESPDUSender
                 
                 firePdu.setLocationInWorldCoordinates(tarLocation);
                 firePdu.setRange((float) 4000.0);
-                byte[] fireArray = firePdu.marshal();
+                byte[] fireArray = firePdu.marshal().array();
 
                 broadcastAddresses = getBroadcastAddresses();
                 Iterator iterator = broadcastAddresses.iterator();
diff --git a/assignments/src/MV3500Cohort2019JulySeptember/homework4/Yurkovich/Yurk_EspduSender.java b/assignments/src/MV3500Cohort2019JulySeptember/homework4/Yurkovich/Yurk_EspduSender.java
index a84dae2fa0c6065f5afae0bc0283760b9040aa1b..2721f764fca2d52b0dcc698261660ed6fe4b3a38 100644
--- a/assignments/src/MV3500Cohort2019JulySeptember/homework4/Yurkovich/Yurk_EspduSender.java
+++ b/assignments/src/MV3500Cohort2019JulySeptember/homework4/Yurkovich/Yurk_EspduSender.java
@@ -281,7 +281,7 @@ public class Yurk_EspduSender
                                 firePdu.setVelocity(pVelocity);
                                 
                                 
-				byte[] fireArray = firePdu.marshal();
+				byte[] fireArray = firePdu.marshal().array();
 
 				broadcastAddresses = getBroadcastAddresses();
 				Iterator iterator = broadcastAddresses.iterator();
diff --git a/assignments/src/MV3500Cohort2019JulySeptember/projects/SchuttFetterolf/AllPduRoundTripTest.java b/assignments/src/MV3500Cohort2019JulySeptember/projects/SchuttFetterolf/AllPduRoundTripTest.java
index bbcfe4e717dfd7db0003ab51451cca56a657e464..76baf9fddefd1b51e20bc1af32204f643af72540 100644
--- a/assignments/src/MV3500Cohort2019JulySeptember/projects/SchuttFetterolf/AllPduRoundTripTest.java
+++ b/assignments/src/MV3500Cohort2019JulySeptember/projects/SchuttFetterolf/AllPduRoundTripTest.java
@@ -190,7 +190,7 @@
 //  private void sendOne(Pdu pdu)
 //  {
 //    pduSentMap.put(pdu.getPduType(), pdu);
-//    disNetworkInterface.send(pdu);
+//    disNetworkInterface.sendPDU(pdu);
 //  }
 //
 //  private void setupRecorder() throws Exception
diff --git a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/BrittSimulation.java b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/BrittSimulation.java
index 8d00a280ae1909c005eb05b2837caf813b381deb..d71bd39fbcbdac32eb6382a3dd287aa01bc8be0d 100644
--- a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/BrittSimulation.java
+++ b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/BrittSimulation.java
@@ -126,7 +126,7 @@ public class BrittSimulation
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/EspduSender.java b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/EspduSender.java
index 42c186b4f66eabbb50c61b53e4a89a2491361d66..bab47f015f1e303e10937221c8ebd7e9828c0410 100644
--- a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/EspduSender.java
+++ b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/EspduSender.java
@@ -315,7 +315,7 @@ public class EspduSender
 
 				FirePdu firePdu = new FirePdu();
                 firePdu.setLocationInWorldCoordinates(espdu.getEntityLocation());
-				byte[] fireArray = firePdu.marshal();
+				byte[] fireArray = firePdu.marshal().array();
 
 //                CommentPdu    newCommentPdu = new CommentPdu();
 //                ArrayList<VariableDatum> payloadList = new ArrayList<>();
diff --git a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/ExampleSimulationProgram.java b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/ExampleSimulationProgram.java
index b32306d44c33083de3613cf8de4d42a994a61fe5..eef45fd5aba7ea473869233494b70b043571980b 100644
--- a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/ExampleSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Britt/ExampleSimulationProgram.java
@@ -116,7 +116,7 @@ public class ExampleSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Cannon/CannonArtillerySimulation.java b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Cannon/CannonArtillerySimulation.java
index 8e610d6d24cc3cfad77aa35f35693c5f9fdd9d08..7487d210eba85d4f5a1c12a43d9ddec745628215 100644
--- a/assignments/src/MV3500Cohort2020JulySeptember/homework4/Cannon/CannonArtillerySimulation.java
+++ b/assignments/src/MV3500Cohort2020JulySeptember/homework4/Cannon/CannonArtillerySimulation.java
@@ -121,7 +121,7 @@ public class CannonArtillerySimulation {
      */
     private void sendSinglePdu(Pdu pdu) {
         try {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } catch (InterruptedException ex) {
             System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
diff --git a/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/test/WhiteSimulation.java b/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/test/WhiteSimulation.java
index e62d50be646327beb4329eb5400bbfea23af8a55..85d406f2ee4f6ec15befabfc35a526548e1b662e 100644
--- a/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/test/WhiteSimulation.java
+++ b/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/test/WhiteSimulation.java
@@ -129,7 +129,7 @@ public class WhiteSimulation
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/working/WhiteSimulation.java b/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/working/WhiteSimulation.java
index b029ce4bb10c12261b93f0a84c609f10427b4078..087f4268fc27c8b82e1a1725fa4664c92b0b0579 100644
--- a/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/working/WhiteSimulation.java
+++ b/assignments/src/MV3500Cohort2020JulySeptember/homework4/White/working/WhiteSimulation.java
@@ -126,7 +126,7 @@ public class WhiteSimulation
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Allen/ExampleSimulationProgramAllen_3.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Allen/ExampleSimulationProgramAllen_3.java
index 33ca72aad1f18a69ecf0914dccab93ebd16597f5..7505ffadb6dd2d49d5b2ace8f0de5ecce152a405 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Allen/ExampleSimulationProgramAllen_3.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Allen/ExampleSimulationProgramAllen_3.java
@@ -395,7 +395,7 @@ public class ExampleSimulationProgramAllen_3
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher_2.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher_2.java
index 49d8edab8812e25e584b3b947ddf575f9aed5b9a..fbd5be06f23f82e80a733ffa606ee6a39a5ec758 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher_2.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Fisher/ExampleSimulationProgramFisher_2.java
@@ -290,7 +290,7 @@ public class ExampleSimulationProgramFisher_2
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignmentThreeSimulation.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignmentThreeSimulation.java
index 836c3e84ca82f37bf215175bd6608f572814d042..da46993c1f0952ecd096c6decfe97ea9eaaa3871 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignmentThreeSimulation.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Frank/FrankAssignmentThreeSimulation.java
@@ -412,7 +412,7 @@ public class FrankAssignmentThreeSimulation {
      */
     private void sendSinglePdu(Pdu pdu) {
         try {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } catch (InterruptedException ex) {
             System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerDom/HittnerDom3HW.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerDom/HittnerDom3HW.java
index e14bf1184e20571d213841a726439dbb686d1bf6..db30b21bc693befa207bc41dd7e31f869a2ad32e 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerDom/HittnerDom3HW.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerDom/HittnerDom3HW.java
@@ -274,7 +274,7 @@ public class HittnerDom3HW
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerNick/HittnerNick3.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerNick/HittnerNick3.java
index 3de8dc0eae187a3ba359205c98f86fee03ec9f1c..52a396d54b4a2eb7938a5beb4ba0e27377b08192 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerNick/HittnerNick3.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/HittnerNick/HittnerNick3.java
@@ -270,7 +270,7 @@ public class HittnerNick3
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Keeven/Keeven3.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Keeven/Keeven3.java
index 2dbb6d64652f53ee4189738ae755ddcee5974664..fbaa589657dcdcf8cfa13b94e1a0df5d1dbceab7 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Keeven/Keeven3.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Keeven/Keeven3.java
@@ -411,7 +411,7 @@ public class Keeven3 {
      */
     private void sendSinglePdu(Pdu pdu) {
         try {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } catch (InterruptedException ex) {
             System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java
index 3b9bf63bdc1133372cbb7e25f82f0376793158de..5cd62faaf97c906d399a4dda8b99c1d87c6dabfd 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Leckie/homework3Leckie.java
@@ -259,7 +259,7 @@ public class homework3Leckie
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/McNeely/ExampleSimulationProgramMcNeely.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/McNeely/ExampleSimulationProgramMcNeely.java
index c6116b78ed2c82c912b5d000911060c6512ceab3..3777ab0d8381a21673e72248be66c8173cb0aef3 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/McNeely/ExampleSimulationProgramMcNeely.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/McNeely/ExampleSimulationProgramMcNeely.java
@@ -251,7 +251,7 @@ public class ExampleSimulationProgramMcNeely
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Morris/MorrisSimulationProgram.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Morris/MorrisSimulationProgram.java
index 978e9a8cdab264f117d31fd7f96339356d9c0876..542c521eb81731e612d17ac0ee1b05eafad3375f 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Morris/MorrisSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Morris/MorrisSimulationProgram.java
@@ -95,7 +95,7 @@ public class MorrisSimulationProgram
 
                 // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoort at!
                 firePdu_1a.setLocationInWorldCoordinates(entityStatePdu_1.getEntityLocation());
-                byte[] fireArray = firePdu_1a.marshal();
+                byte[] fireArray = firePdu_1a.marshal().array();
 
                 System.out.println("FirePdu_1 #" + simulationLoopCount + " firePdu=[FireMissionIndex=" + firePdu_1a.getFireMissionIndex() + ", descriptor=" + firePdu_1a.getDescriptor() + "]");
             // etc. etc. your code goes here for your simulation of interest
@@ -279,7 +279,7 @@ public class MorrisSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Pugh/PughSimulationProgram.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Pugh/PughSimulationProgram.java
index 58dd7652a43e4d31cbdde92a48fad919ecbcdd96..f45e97677c45267ba7ff7122e613d8de62af5f11 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Pugh/PughSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Pugh/PughSimulationProgram.java
@@ -279,7 +279,7 @@ public class PughSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Reynolds/ExampleSimulationProgramReynolds.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Reynolds/ExampleSimulationProgramReynolds.java
index 13ca42dfa88849cff312d39ece3de90ff9f00d68..f5041be5084b42b78a2a10aa1b753cb15e21e2ea 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Reynolds/ExampleSimulationProgramReynolds.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Reynolds/ExampleSimulationProgramReynolds.java
@@ -405,7 +405,7 @@ public class ExampleSimulationProgramReynolds {
      */
     private void sendSinglePdu(Pdu pdu) {
         try {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } catch (InterruptedException ex) {
             System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Robinson/hw3Robinson.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Robinson/hw3Robinson.java
index 64d7bd0c560082e2508a2469701d0ea69306a685..99c678a0a315b2d07a4940c05bd33f9effda0ea7 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Robinson/hw3Robinson.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Robinson/hw3Robinson.java
@@ -304,7 +304,7 @@ public class hw3Robinson {
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/ExampleSimulationProgram.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/ExampleSimulationProgram.java
index d30e20e033263fd0dfa85a253a0f82f331fbdcfe..f3637760cb1f3e4404e5b2b2931ccc1d7d6def69 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/ExampleSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/ExampleSimulationProgram.java
@@ -282,7 +282,7 @@ public class ExampleSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/SchlesselSimulationProgram.java b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/SchlesselSimulationProgram.java
index 34956e4a56dd44c556d6e7c7ad5cbad1991d4301..7957fd606116951dc9da15f59c641833c2de45c0 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/SchlesselSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/homework3/Schlessel/SchlesselSimulationProgram.java
@@ -289,7 +289,7 @@ public class SchlesselSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/projects/AllenReynolds/KineticFirewallSimulationProgramAllenReynolds.java b/assignments/src/MV3500Cohort2021JulySeptember/projects/AllenReynolds/KineticFirewallSimulationProgramAllenReynolds.java
index 5dba8f8d2917270db8cf813be0f2dc822ecc9b0e..171cea5b8e8f7c95b154f5ac654961b6c03dc887 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/projects/AllenReynolds/KineticFirewallSimulationProgramAllenReynolds.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/projects/AllenReynolds/KineticFirewallSimulationProgramAllenReynolds.java
@@ -457,7 +457,7 @@ public class KineticFirewallSimulationProgramAllenReynolds {
      */
     private void sendSinglePdu(Pdu pdu) {
         try {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } catch (InterruptedException ex) {
             System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
diff --git a/assignments/src/MV3500Cohort2021JulySeptember/projects/Fisher/MV3500ProjectFisher.java b/assignments/src/MV3500Cohort2021JulySeptember/projects/Fisher/MV3500ProjectFisher.java
index a3050a7b4f0e1ecd907986fa5bac3cb7e7166c0c..452b2e00176b02acc5456ad33a47e21873fdfc48 100644
--- a/assignments/src/MV3500Cohort2021JulySeptember/projects/Fisher/MV3500ProjectFisher.java
+++ b/assignments/src/MV3500Cohort2021JulySeptember/projects/Fisher/MV3500ProjectFisher.java
@@ -396,7 +396,7 @@ public class MV3500ProjectFisher
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/ExampleSimulationProgramAshmore.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/ExampleSimulationProgramAshmore.java
index 256bee4813b8afe930c6cfb086f6b8fdf1b96465..9115997c159ac4a282c811873f37a1e62b3a5122 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/ExampleSimulationProgramAshmore.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/ExampleSimulationProgramAshmore.java
@@ -355,7 +355,7 @@ public class ExampleSimulationProgramAshmore
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/PduTrack.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/PduTrack.java
index 59ed259f6e77010678f1ff5da4aee78e9b499d1b..8cded7a666c68aaf99dad80444611ca0cc6a351a 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/PduTrack.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Ashmore/PduTrack.java
@@ -954,7 +954,7 @@ public class PduTrack
                 EntityStatePdu espdu = (EntityStatePdu) pdu;
                 System.out.println("espdu from pduTrack pduList");
                 reportPdu(espdu);
-                byte[] byteArray = espdu.marshal();
+                byte[] byteArray = espdu.marshal().array();
                 System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
                 flushBuffers();
                 
@@ -972,7 +972,7 @@ public class PduTrack
                 
                 System.out.println("espdu.copyByteBuffer()");
                 reportPdu(espdu.copyByteBuffer());
-                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal();
+                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal().array();
                 System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
                 flushBuffers();
                 
@@ -990,7 +990,7 @@ public class PduTrack
                 
                 System.out.println("espdu.copyDataOutputStream()");
                 reportPdu(espdu.copyDataOutputStream());
-                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal();
+                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal().array();
                 System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
                 flushBuffers();
                 
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/ExampleSimulationProgramDuran.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/ExampleSimulationProgramDuran.java
index c544a0ebc8f7318dfa8be3abca3f5f51c4677ee4..9d93215fa55f4a7ba8138a75630cda5743b3535e 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/ExampleSimulationProgramDuran.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/ExampleSimulationProgramDuran.java
@@ -356,7 +356,7 @@ public class ExampleSimulationProgramDuran
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/PduTrack.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/PduTrack.java
index 61a7159852d5a52117479d7a4428abdadd99fbf0..121ab78128723a99e57878f115e03b698985b89f 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/PduTrack.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Duran/PduTrack.java
@@ -954,7 +954,7 @@ public class PduTrack
                 EntityStatePdu espdu = (EntityStatePdu) pdu;
                 System.out.println("espdu from pduTrack pduList");
                 reportPdu(espdu);
-                byte[] byteArray = espdu.marshal();
+                byte[] byteArray = espdu.marshal().array();
                 System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
                 flushBuffers();
                 
@@ -972,7 +972,7 @@ public class PduTrack
                 
                 System.out.println("espdu.copyByteBuffer()");
                 reportPdu(espdu.copyByteBuffer());
-                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal();
+                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal().array();
                 System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
                 flushBuffers();
                 
@@ -990,7 +990,7 @@ public class PduTrack
                 
                 System.out.println("espdu.copyDataOutputStream()");
                 reportPdu(espdu.copyDataOutputStream());
-                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal();
+                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal().array();
                 System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
                 flushBuffers();
                 
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/ExampleSimulationProgramHickey.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/ExampleSimulationProgramHickey.java
index 6da4e4e01d4c48f6abb0a07c3b9b4b93bd15aa7a..46a48a53ec8ad47a70eae79465219a0da638d136 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/ExampleSimulationProgramHickey.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/ExampleSimulationProgramHickey.java
@@ -1,505 +1,505 @@
-/**
- * Copyright (c) 2008-2022, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
- * This work is provided under a BSD open-source license, see project license.html and license.txt
- * @author brutzman@nps.edu
- */
-package MV3500Cohort2022MayJune.homework2.Hickey;
-
-import MV3500Cohort2022MayJune.homework2.Tam.*;
-import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
-import edu.nps.moves.dis7.entities.swe.platform.surface._002Triton;
-import edu.nps.moves.dis7.enumerations.*;
-import edu.nps.moves.dis7.pdus.*;
-import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface;
-import edu.nps.moves.dis7.utilities.DisTime;
-import edu.nps.moves.dis7.utilities.PduFactory;
-import edu.nps.moves.dis7.utilities.SimulationManager;
-import edu.nps.moves.dis7.utilities.stream.PduRecorder;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/** The purpose of this inheritable class is to provide an easily modifiable example simulation program
- *  that includes DIS-capable entities performing tasks of interest, and then reporting activity via PDUs
- *  to the network.
- *  Default program initialization includes PDU recording turned on by default.
- *  @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramLog.txt">ExampleSimulationProgramLog.txt</a>
- */
-public class ExampleSimulationProgramHickey
-{
-    private      boolean verboseComments         = true;
-    static final String  NETWORK_ADDRESS_DEFAULT  = "239.1.2.3";
-    static final int     NETWORK_PORT_DEFAULT     = 3000;
-                 String  networkAddress           = NETWORK_ADDRESS_DEFAULT;
-                 int     networkPort              = NETWORK_PORT_DEFAULT;
-                 String  thisHostName             = "localhost";
-                 String  DEFAULT_OUTPUT_DIRECTORY = "./pduLog";
-    
-    /** seconds for real-time execution (not simulation time, which may or may not be the same) */
-    double  currentTimeStep  =  2.0; // seconds
-    /** initial simulation time */
-    double  initialTime = 0.0;
-    /** current simulation time */
-    double  simulationTime;
-
-    /**
-     * Output prefix to help with logging by identifying this class (can be overridden in subclass).
-     */
-    protected static String TRACE_PREFIX;
-    
-    /* Declare DIS Protocol Data Unit (PDU) classes for simulation entities */
-    
-    DisTime.TimestampStyle       timestampStyle      = DisTime.TimestampStyle.IEEE_ABSOLUTE;
-    PduFactory                   pduFactory          = new PduFactory(timestampStyle);
-    
-    /** EntityID settings for entity 1 */
-    protected EntityID           entityID_1          = new EntityID();
-    /** EntityID settings for entity 2 */
-    protected EntityID           entityID_2          = new EntityID();
-    /** ESPDU for entity 1 */
-    protected EntityStatePdu     entityStatePdu_1    = pduFactory.makeEntityStatePdu();
-    /** ESPDU for entity 2 */
-    protected EntityStatePdu     entityStatePdu_2    = pduFactory.makeEntityStatePdu();
-    /** FirePdu for entity 1 first  weapon (if any) */
-    protected FirePdu            firePdu_1a          = pduFactory.makeFirePdu();
-    /** FirePdu for entity 1 second weapon (if any) */
-    protected FirePdu            firePdu_1b          = pduFactory.makeFirePdu();
-    /** MunitionDescriptor for these weapons */
-    protected MunitionDescriptor munitionDescriptor1 = new MunitionDescriptor();
-    
-    /** this class instantiated as an object */
-    SimulationManager        simulationManager = new SimulationManager();
-    
-    /** Get ready, get set... initialize simulation entities
-     */
-    public void initializeSimulationEntities()
-    {
-        // Your model setup: define participants.  who's who in this zoo?
-        // Assuming you keep track of entity objects...  here is some support for for Entity 1.
-        
-        // PDU objects are already created, now set their values.
-        entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
-        
-        entityID_2.setSiteID(1).setApplicationID(2).setEntityID(4); // made-up example ID; 
-        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
-
-        entityStatePdu_1.setEntityID(entityID_1);
-        entityStatePdu_1.setForceId(ForceID.FRIENDLY);
-        entityStatePdu_1.setEntityType(new _001Poseidon()); // note import statement above
-        entityStatePdu_1.setMarking("Entity #1");
-        entityStatePdu_1.getMarkingString(); // check
-
-        entityStatePdu_2.setEntityID(entityID_2);
-        entityStatePdu_2.setForceId(ForceID.OPPOSING);
-        entityStatePdu_2.setEntityType(new _002Triton()); // note import statement above
-        entityStatePdu_2.setMarking("Entity #2");
-
-        // TODO how should we customize this munition?  what is it for your simulation?
-        munitionDescriptor1.setQuantity(1);
-        firePdu_1a.setDescriptor(munitionDescriptor1).setRange(1000.0f);
-        
-        // TODO simulation management PDUs for startup, planning to design special class support 
-//        simulationManager.addEntity();
-        simulationManager.setDescriptor("ExampleSimulationProgram");
-        simulationManager.addHost(thisHostName);
-        simulationManager.setDisThreadedNetworkInterface(disNetworkInterface);
-    }
-                 
-    /**
-     * This runSimulationLoops() method is for you, a customizable programmer-modifiable
-     * code block for defining and running a new simulation of interest.
-     * 
-     * Welcome! Other parts of this program handle bookkeeping and plumbing tasks so that
-     * you can focus on your model entities and activities.
-     * Expandable support includes DIS EntityStatePdu, FirePdu and CommentPdu all available for 
-     * modification and sending in a simulation loop.
-     * Continuous improvement efforts seek to make this program as easy and straightforward
-     * as possible for DIS simulationists to use and adapt.
-     * All of the other methods are setup, teardown and configuration that you may find
-     * interesting, even helpful, but don't really have to worry about.
-     */
-    @SuppressWarnings("SleepWhileInLoop") // yes we do that
-    public void runSimulationLoops ()
-    {
-      try
-      {              
-        final int     SIMULATION_MAX_LOOP_COUNT = 10; // be deliberate out there!  also avoid infinite loops.
-              int     simulationLoopCount = 0;        // variable, initialized at 0
-              boolean simulationComplete = false;     // sentinel variable as termination condition, are we done yet?
-        
-        // TODO reset Clock Time to today's date and timestamp to zero, providing consistent outputs for each simulation run
-
-        pduRecorder.setVerbose(true);
-        
-        initializeSimulationEntities();
-        
-        simulationManager.simulationJoin();
-        simulationManager.simulationStart();
-        
-        // ===================================================================================================
-        // loop the simulation while allowed, programmer can set additional conditions to break out and finish
-        while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT)  // are we done yet?
-        {
-            simulationLoopCount++; // good practice: increment loop counter as first action in that loop
-            
-            // =============================================================================================
-            // * your own simulation code starts here! *
-            // =============================================================================================
-            
-            //  are there any other variables to modify at the beginning of your loop?
-            
-            // compute a track, update an ESPDU, whatever it is that your model is doing...
-            
-            // Where is my entity?  Insert changes in position; this sample only changes X position.
-            entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() + 1.0); // 1m per timestep
-            
-            // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoot at!
-            
-            // etc. etc. your code goes here for your simulation of interest
-                
-            // something happens between my simulation entities, la de da de da...
-            System.out.println ("... My simulation just did something, no really...");
-            System.out.flush();
-            
-            
-            // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
-            narrativeMessage1 = "MV3500 ExampleSimulationProgram";
-            narrativeMessage2 = "runSimulation() loop " + simulationLoopCount;
-            narrativeMessage3 = ""; // intentionally blank for testing
-
-            // your loop termination condition goes here
-            if (simulationLoopCount > 4) // for example
-            {
-                simulationComplete = true;
-            }      
-            // =============================================================================================
-            // * your own simulation code is finished here! *
-            // =============================================================================================
-            
-            // staying synchronized with timestep: wait duration for elapsed time in this loop
-            // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
-            Thread.sleep((long)(currentTimeStep * 1000)); // seconds * (1000 msec/sec) = milliseconds
-            System.out.println ("... [Pausing for " + currentTimeStep + " seconds]");
-            
-            // OK now send the status PDUs for this loop, and then continue
-            System.out.println ("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
-            System.out.flush();
-            sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu_1a, currentTimeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
-            sendSinglePdu(entityStatePdu_2); // me too i.e. 2!
-            System.out.println ("... [PDUs successfully sent for this loop]");
-            System.out.flush();
-            
-            // ===============================
-            // current loop now finished, check whether to terminate if simulation complete, otherwise continue
-            if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
-            {
-                System.out.println ("... [loop termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + 
-                System.out.flush();
-                break;
-            }
-        }   // end of simulation loop
-        // ===================================================================================================
-
-        narrativeMessage2 = "runSimulation() completed successfully"; // all done
-        sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
-        System.out.println ("... [final CommentPdu successfully sent for simulation]");
-        
-        // TODO simulation management PDUs
-        simulationManager.simulationStop();
-        simulationManager.simulationLeave();
-      } 
-      catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
-      {
-        Logger.getLogger(ExampleSimulationProgramHickey.class.getName()).log(Level.SEVERE, null, iex);
-      }
-    }
-    /* **************************** infrastructure code, modification is seldom needed ************************* */
-                 
-    String narrativeMessage1 = new String();
-    String narrativeMessage2 = new String();
-    String narrativeMessage3 = new String();
-          
-    /* VariableRecordType enumerations have potential use with CommentPdu logs */
-    /* TODO contrast to EntityType */
-    VariableRecordType     descriptionComment = VariableRecordType.DESCRIPTION;
-    VariableRecordType       narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT;
-    VariableRecordType          statusComment = VariableRecordType.APPLICATION_STATUS;
-    VariableRecordType currentTimeStepComment = VariableRecordType.APPLICATION_TIMESTEP;
-    VariableRecordType           otherComment = VariableRecordType.OTHER;
-    
-    // class variables
-    DisThreadedNetworkInterface             disNetworkInterface;
-    DisThreadedNetworkInterface.PduListener pduListener;
-    Pdu                                     receivedPdu;
-    PduRecorder                             pduRecorder;
-    
-    /**
-     * Constructor design goal: additional built-in initialization conveniences can go here
-     * to keep student efforts focused on the runSimulation() method.
-     */
-    public ExampleSimulationProgramHickey()
-    {
-        DisTime.setTimestampStyle(timestampStyle);
-        
-        try
-        {
-            thisHostName = InetAddress.getLocalHost().getHostName();
-            System.out.println(TRACE_PREFIX + "thisHostName=" + thisHostName);
-        }
-        catch (UnknownHostException uhe)
-        {
-            System.out.println(TRACE_PREFIX + thisHostName + "not connected to network: " + uhe.getMessage());
-        }
-    }
-    
-    /**
-     * Utility Constructor that allows your example simulation program to override default network address and port
-     * @param address network address to use
-     * @param port corresponding network port to use
-     */
-    public ExampleSimulationProgramHickey(String address, int port)
-    {
-        super();
-        
-        setNetworkAddress(address);
-        
-        setNetworkPort(port);
-    }
-
-    /**
-     * get current networkAddress as a string
-     * @return the networkAddress
-     */
-    public String getNetworkAddress()
-    {
-        return networkAddress;
-    }
-    /**
-     * set current networkAddress using a string
-     * @param newNetworkAddress the networkAddress to set
-     */
-    public final void setNetworkAddress(String newNetworkAddress)
-    {
-        this.networkAddress = newNetworkAddress;
-    }
-
-    /**
-     * get current networkPort
-     * @return the networkPort
-     */
-    public int getNetworkPort()
-    {
-        return networkPort;
-    }
-    /**
-     * set current networkPort
-     * @param newNetworkPort the networkPort to set
-     */
-    public final void setNetworkPort(int newNetworkPort)
-    {
-        this.networkPort = newNetworkPort;
-    }
-    /**
-     * Get timestampStyle used by PduFactory
-     * @return current timestampStyle
-     */
-    public DisTime.TimestampStyle getTimestampStyle()
-    {
-        return timestampStyle;
-    }
-    /**
-     * Set timestampStyle used by PduFactory
-     * @param newTimestampStyle the timestampStyle to set
-     * @return same object to permit progressive setters 
-     */
-    public ExampleSimulationProgramHickey setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
-    {
-        timestampStyle = newTimestampStyle;
-        DisTime.setTimestampStyle(newTimestampStyle);
-        return this;
-    }
-
-    /**
-     * Initialize network interface, choosing best available network interface
-     */
-    public void setUpNetworkInterface()
-    {
-        disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort());
-        disNetworkInterface.setDescriptor ("ExampleSimulationProgram pdu looping");
-        
-        System.out.println("Network confirmation:" +
-               " address=" + disNetworkInterface.getAddress()+ //  disNetworkInterface.getMulticastGroup() + 
-                  " port=" + disNetworkInterface.getPort());   // + disNetworkInterface.getDisPort());
-        pduListener = new DisThreadedNetworkInterface.PduListener()
-        {
-            /** Callback handler for listener */
-            @Override
-            public void incomingPdu(Pdu newPdu)
-            {
-                receivedPdu = newPdu;
-            }
-        };
-        disNetworkInterface.addListener(pduListener);
-        
-        String outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
-        System.out.println("Beginning pdu save to directory " + outputDirectory);
-        pduRecorder = new PduRecorder(outputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save
-        pduRecorder.setEncodingPduLog(PduRecorder.ENCODING_PLAINTEXT);
-        pduRecorder.setVerbose(true); // either sending, receiving or both
-        pduRecorder.start(); // begin running
-    }
-
-    /** All done, release network resources */
-    public void tearDownNetworkInterface()
-    {
-        pduRecorder.stop(); // handles disNetworkInterface.close(), tears down threads and sockets
-    }
-
-    /** 
-     * Send a single Protocol Data Unit (PDU) of any type
-     * @param pdu the pdu to send
-     */
-    protected void sendSinglePdu(Pdu pdu)
-    {
-        try
-        {
-            disNetworkInterface.send(pdu);
-            Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
-        } 
-        catch (InterruptedException ex)
-        {
-            System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
-            System.exit(1);
-        }
-    }
-
-    /**
-     * Send Comment PDU
-     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
-     * @param commentType    enumeration value describing purpose of the narrative comment
-     * @param comments       String array of narrative comments
-     */
-    public void sendCommentPdu(VariableRecordType commentType,
-                                     // vararg... variable-length set of String comments can optionally follow
-                                        String... comments)
-    {
-        sendAllPdusForLoopTimestep (null, null, commentType, comments);
-    }
-
-    /**
-     * Send EntityState, Fire, Comment PDUs that got updated for this loop, reflecting state of current simulation timestep.
-     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
-     * @param entityStatePdu the ESPDU to send, if any
-     * @param firePdu        the FirePDU to send, if any
-     * @param commentType    enumeration value describing purpose of the narrative comment
-     * @param comments       String array of narrative comments
-     */
-    public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu,
-                                   FirePdu firePdu,
-                        VariableRecordType commentType,
-                              // vararg... variable-length set of String comments can optionally follow
-                                 String... comments)
-    {
-        if (entityStatePdu != null)
-            sendSinglePdu(entityStatePdu);
-            
-        if (firePdu != null)
-            sendSinglePdu(firePdu); // bang
-        
-        if ((comments != null) && (comments.length > 0))
-        {
-            ArrayList<String> newCommentsList = new ArrayList<>();
-            for (String comment : comments)
-            {
-                if (!comment.isEmpty())
-                {
-                    newCommentsList.add(comment); // OK found something to send
-                }
-            }
-            if (!newCommentsList.isEmpty())
-            {
-                if (commentType == null)
-                    commentType = otherComment; // fallback value otherComment
-                // now build the commentPdu from these string inputs, thus constructing a narrative entry
-                CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments);
-                sendSinglePdu(commentPdu);
-                if (isVerboseComments())
-                {
-                    System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString());
-                    System.out.flush();
-                }
-            }
-        }
-    }
-
-    /**
-     * test for verboseComments mode
-     * @return whether verboseComments mode is enabled
-     */
-    public boolean isVerboseComments() {
-        return verboseComments;
-    }
-
-    /**
-     * set verboseComments mode
-     * @param newVerboseComments whether verboseComments mode is enabled
-     */
-    public void setVerboseComments(boolean newVerboseComments) {
-        this.verboseComments = newVerboseComments;
-    }
-    
-    /**
-     * Initial execution via main() method: handle args array of command-line initialization (CLI) arguments here
-     * @param args command-line parameters: network address and port
-     */
-    protected void handleArgs (String[] args)
-    {
-        // initial execution: handle args array of initialization arguments here
-        if (args.length == 2) 
-        {
-            if ((args[0] != null) && !args[0].isEmpty())
-                thisProgram.setNetworkAddress(args[0]);
-            if ((args[1] != null) && !args[1].isEmpty())
-                thisProgram.setNetworkPort(Integer.parseInt(args[1]));
-        }
-        else if (args.length != 0) 
-        {
-            System.err.println("Usage: " + thisProgram.getClass().getSimpleName() + " [address port]");
-            System.exit(-1);
-        }
-    }
-    
-    /** Locally instantiable copy of program, can be subclassed. */
-    protected static ExampleSimulationProgramHickey thisProgram;
-  
-    /**
-     * Main method is first executed when a program instance is loaded.
-     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
-     * @param args command-line parameters: network address and port.
-     *    Command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
-     */
-    public static void main(String[] args)
-    {
-        TRACE_PREFIX = "[" + ExampleSimulationProgramHickey.class.getName() + "] ";
-        
-        System.out.println(TRACE_PREFIX + "main() started...");
-        
-        thisProgram = new ExampleSimulationProgramHickey(); // creates instance of self within static main() method
-        
-        thisProgram.handleArgs (args); // process command-line invocation arguments
-
-        thisProgram.setUpNetworkInterface();
-        
-//        thisProgram.pduRecorder.setDescriptor (TRACE_PREFIX.replace("[","").replace("]","") + " pduRecorder");
-
-        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
-        
-        thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering
-        
-        System.out.println(TRACE_PREFIX + "complete."); // report successful completion
-        
-        System.exit(0); // ensure all threads and sockets released
-    }
-}
+/**
+ * Copyright (c) 2008-2022, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
+ * This work is provided under a BSD open-source license, see project license.html and license.txt
+ * @author brutzman@nps.edu
+ */
+package MV3500Cohort2022MayJune.homework2.Hickey;
+
+import MV3500Cohort2022MayJune.homework2.Tam.*;
+import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
+import edu.nps.moves.dis7.entities.swe.platform.surface._002Triton;
+import edu.nps.moves.dis7.enumerations.*;
+import edu.nps.moves.dis7.pdus.*;
+import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface;
+import edu.nps.moves.dis7.utilities.DisTime;
+import edu.nps.moves.dis7.utilities.PduFactory;
+import edu.nps.moves.dis7.utilities.SimulationManager;
+import edu.nps.moves.dis7.utilities.stream.PduRecorder;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/** The purpose of this inheritable class is to provide an easily modifiable example simulation program
+ *  that includes DIS-capable entities performing tasks of interest, and then reporting activity via PDUs
+ *  to the network.
+ *  Default program initialization includes PDU recording turned on by default.
+ *  @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramLog.txt">ExampleSimulationProgramLog.txt</a>
+ */
+public class ExampleSimulationProgramHickey
+{
+    private      boolean verboseComments         = true;
+    static final String  NETWORK_ADDRESS_DEFAULT  = "239.1.2.3";
+    static final int     NETWORK_PORT_DEFAULT     = 3000;
+                 String  networkAddress           = NETWORK_ADDRESS_DEFAULT;
+                 int     networkPort              = NETWORK_PORT_DEFAULT;
+                 String  thisHostName             = "localhost";
+                 String  DEFAULT_OUTPUT_DIRECTORY = "./pduLog";
+    
+    /** seconds for real-time execution (not simulation time, which may or may not be the same) */
+    double  currentTimeStep  =  2.0; // seconds
+    /** initial simulation time */
+    double  initialTime = 0.0;
+    /** current simulation time */
+    double  simulationTime;
+
+    /**
+     * Output prefix to help with logging by identifying this class (can be overridden in subclass).
+     */
+    protected static String TRACE_PREFIX;
+    
+    /* Declare DIS Protocol Data Unit (PDU) classes for simulation entities */
+    
+    DisTime.TimestampStyle       timestampStyle      = DisTime.TimestampStyle.IEEE_ABSOLUTE;
+    PduFactory                   pduFactory          = new PduFactory(timestampStyle);
+    
+    /** EntityID settings for entity 1 */
+    protected EntityID           entityID_1          = new EntityID();
+    /** EntityID settings for entity 2 */
+    protected EntityID           entityID_2          = new EntityID();
+    /** ESPDU for entity 1 */
+    protected EntityStatePdu     entityStatePdu_1    = pduFactory.makeEntityStatePdu();
+    /** ESPDU for entity 2 */
+    protected EntityStatePdu     entityStatePdu_2    = pduFactory.makeEntityStatePdu();
+    /** FirePdu for entity 1 first  weapon (if any) */
+    protected FirePdu            firePdu_1a          = pduFactory.makeFirePdu();
+    /** FirePdu for entity 1 second weapon (if any) */
+    protected FirePdu            firePdu_1b          = pduFactory.makeFirePdu();
+    /** MunitionDescriptor for these weapons */
+    protected MunitionDescriptor munitionDescriptor1 = new MunitionDescriptor();
+    
+    /** this class instantiated as an object */
+    SimulationManager        simulationManager = new SimulationManager();
+    
+    /** Get ready, get set... initialize simulation entities
+     */
+    public void initializeSimulationEntities()
+    {
+        // Your model setup: define participants.  who's who in this zoo?
+        // Assuming you keep track of entity objects...  here is some support for for Entity 1.
+        
+        // PDU objects are already created, now set their values.
+        entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
+        
+        entityID_2.setSiteID(1).setApplicationID(2).setEntityID(4); // made-up example ID; 
+        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
+
+        entityStatePdu_1.setEntityID(entityID_1);
+        entityStatePdu_1.setForceId(ForceID.FRIENDLY);
+        entityStatePdu_1.setEntityType(new _001Poseidon()); // note import statement above
+        entityStatePdu_1.setMarking("Entity #1");
+        entityStatePdu_1.getMarkingString(); // check
+
+        entityStatePdu_2.setEntityID(entityID_2);
+        entityStatePdu_2.setForceId(ForceID.OPPOSING);
+        entityStatePdu_2.setEntityType(new _002Triton()); // note import statement above
+        entityStatePdu_2.setMarking("Entity #2");
+
+        // TODO how should we customize this munition?  what is it for your simulation?
+        munitionDescriptor1.setQuantity(1);
+        firePdu_1a.setDescriptor(munitionDescriptor1).setRange(1000.0f);
+        
+        // TODO simulation management PDUs for startup, planning to design special class support 
+//        simulationManager.addEntity();
+        simulationManager.setDescriptor("ExampleSimulationProgram");
+        simulationManager.addHost(thisHostName);
+        simulationManager.setDisThreadedNetworkInterface(disNetworkInterface);
+    }
+                 
+    /**
+     * This runSimulationLoops() method is for you, a customizable programmer-modifiable
+     * code block for defining and running a new simulation of interest.
+     * 
+     * Welcome! Other parts of this program handle bookkeeping and plumbing tasks so that
+     * you can focus on your model entities and activities.
+     * Expandable support includes DIS EntityStatePdu, FirePdu and CommentPdu all available for 
+     * modification and sending in a simulation loop.
+     * Continuous improvement efforts seek to make this program as easy and straightforward
+     * as possible for DIS simulationists to use and adapt.
+     * All of the other methods are setup, teardown and configuration that you may find
+     * interesting, even helpful, but don't really have to worry about.
+     */
+    @SuppressWarnings("SleepWhileInLoop") // yes we do that
+    public void runSimulationLoops ()
+    {
+      try
+      {              
+        final int     SIMULATION_MAX_LOOP_COUNT = 10; // be deliberate out there!  also avoid infinite loops.
+              int     simulationLoopCount = 0;        // variable, initialized at 0
+              boolean simulationComplete = false;     // sentinel variable as termination condition, are we done yet?
+        
+        // TODO reset Clock Time to today's date and timestamp to zero, providing consistent outputs for each simulation run
+
+        pduRecorder.setVerbose(true);
+        
+        initializeSimulationEntities();
+        
+        simulationManager.simulationJoin();
+        simulationManager.simulationStart();
+        
+        // ===================================================================================================
+        // loop the simulation while allowed, programmer can set additional conditions to break out and finish
+        while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT)  // are we done yet?
+        {
+            simulationLoopCount++; // good practice: increment loop counter as first action in that loop
+            
+            // =============================================================================================
+            // * your own simulation code starts here! *
+            // =============================================================================================
+            
+            //  are there any other variables to modify at the beginning of your loop?
+            
+            // compute a track, update an ESPDU, whatever it is that your model is doing...
+            
+            // Where is my entity?  Insert changes in position; this sample only changes X position.
+            entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() + 1.0); // 1m per timestep
+            
+            // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoot at!
+            
+            // etc. etc. your code goes here for your simulation of interest
+                
+            // something happens between my simulation entities, la de da de da...
+            System.out.println ("... My simulation just did something, no really...");
+            System.out.flush();
+            
+            
+            // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
+            narrativeMessage1 = "MV3500 ExampleSimulationProgram";
+            narrativeMessage2 = "runSimulation() loop " + simulationLoopCount;
+            narrativeMessage3 = ""; // intentionally blank for testing
+
+            // your loop termination condition goes here
+            if (simulationLoopCount > 4) // for example
+            {
+                simulationComplete = true;
+            }      
+            // =============================================================================================
+            // * your own simulation code is finished here! *
+            // =============================================================================================
+            
+            // staying synchronized with timestep: wait duration for elapsed time in this loop
+            // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
+            Thread.sleep((long)(currentTimeStep * 1000)); // seconds * (1000 msec/sec) = milliseconds
+            System.out.println ("... [Pausing for " + currentTimeStep + " seconds]");
+            
+            // OK now send the status PDUs for this loop, and then continue
+            System.out.println ("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
+            System.out.flush();
+            sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu_1a, currentTimeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
+            sendSinglePdu(entityStatePdu_2); // me too i.e. 2!
+            System.out.println ("... [PDUs successfully sent for this loop]");
+            System.out.flush();
+            
+            // ===============================
+            // current loop now finished, check whether to terminate if simulation complete, otherwise continue
+            if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
+            {
+                System.out.println ("... [loop termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + 
+                System.out.flush();
+                break;
+            }
+        }   // end of simulation loop
+        // ===================================================================================================
+
+        narrativeMessage2 = "runSimulation() completed successfully"; // all done
+        sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
+        System.out.println ("... [final CommentPdu successfully sent for simulation]");
+        
+        // TODO simulation management PDUs
+        simulationManager.simulationStop();
+        simulationManager.simulationLeave();
+      } 
+      catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
+      {
+        Logger.getLogger(ExampleSimulationProgramHickey.class.getName()).log(Level.SEVERE, null, iex);
+      }
+    }
+    /* **************************** infrastructure code, modification is seldom needed ************************* */
+                 
+    String narrativeMessage1 = new String();
+    String narrativeMessage2 = new String();
+    String narrativeMessage3 = new String();
+          
+    /* VariableRecordType enumerations have potential use with CommentPdu logs */
+    /* TODO contrast to EntityType */
+    VariableRecordType     descriptionComment = VariableRecordType.DESCRIPTION;
+    VariableRecordType       narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT;
+    VariableRecordType          statusComment = VariableRecordType.APPLICATION_STATUS;
+    VariableRecordType currentTimeStepComment = VariableRecordType.APPLICATION_TIMESTEP;
+    VariableRecordType           otherComment = VariableRecordType.OTHER;
+    
+    // class variables
+    DisThreadedNetworkInterface             disNetworkInterface;
+    DisThreadedNetworkInterface.PduListener pduListener;
+    Pdu                                     receivedPdu;
+    PduRecorder                             pduRecorder;
+    
+    /**
+     * Constructor design goal: additional built-in initialization conveniences can go here
+     * to keep student efforts focused on the runSimulation() method.
+     */
+    public ExampleSimulationProgramHickey()
+    {
+        DisTime.setTimestampStyle(timestampStyle);
+        
+        try
+        {
+            thisHostName = InetAddress.getLocalHost().getHostName();
+            System.out.println(TRACE_PREFIX + "thisHostName=" + thisHostName);
+        }
+        catch (UnknownHostException uhe)
+        {
+            System.out.println(TRACE_PREFIX + thisHostName + "not connected to network: " + uhe.getMessage());
+        }
+    }
+    
+    /**
+     * Utility Constructor that allows your example simulation program to override default network address and port
+     * @param address network address to use
+     * @param port corresponding network port to use
+     */
+    public ExampleSimulationProgramHickey(String address, int port)
+    {
+        super();
+        
+        setNetworkAddress(address);
+        
+        setNetworkPort(port);
+    }
+
+    /**
+     * get current networkAddress as a string
+     * @return the networkAddress
+     */
+    public String getNetworkAddress()
+    {
+        return networkAddress;
+    }
+    /**
+     * set current networkAddress using a string
+     * @param newNetworkAddress the networkAddress to set
+     */
+    public final void setNetworkAddress(String newNetworkAddress)
+    {
+        this.networkAddress = newNetworkAddress;
+    }
+
+    /**
+     * get current networkPort
+     * @return the networkPort
+     */
+    public int getNetworkPort()
+    {
+        return networkPort;
+    }
+    /**
+     * set current networkPort
+     * @param newNetworkPort the networkPort to set
+     */
+    public final void setNetworkPort(int newNetworkPort)
+    {
+        this.networkPort = newNetworkPort;
+    }
+    /**
+     * Get timestampStyle used by PduFactory
+     * @return current timestampStyle
+     */
+    public DisTime.TimestampStyle getTimestampStyle()
+    {
+        return timestampStyle;
+    }
+    /**
+     * Set timestampStyle used by PduFactory
+     * @param newTimestampStyle the timestampStyle to set
+     * @return same object to permit progressive setters 
+     */
+    public ExampleSimulationProgramHickey setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
+    {
+        timestampStyle = newTimestampStyle;
+        DisTime.setTimestampStyle(newTimestampStyle);
+        return this;
+    }
+
+    /**
+     * Initialize network interface, choosing best available network interface
+     */
+    public void setUpNetworkInterface()
+    {
+        disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort());
+        disNetworkInterface.setDescriptor ("ExampleSimulationProgram pdu looping");
+        
+        System.out.println("Network confirmation:" +
+               " address=" + disNetworkInterface.getAddress()+ //  disNetworkInterface.getMulticastGroup() + 
+                  " port=" + disNetworkInterface.getPort());   // + disNetworkInterface.getDisPort());
+        pduListener = new DisThreadedNetworkInterface.PduListener()
+        {
+            /** Callback handler for listener */
+            @Override
+            public void incomingPdu(Pdu newPdu)
+            {
+                receivedPdu = newPdu;
+            }
+        };
+        disNetworkInterface.addListener(pduListener);
+        
+        String outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
+        System.out.println("Beginning pdu save to directory " + outputDirectory);
+        pduRecorder = new PduRecorder(outputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save
+        pduRecorder.setEncodingPduLog(PduRecorder.ENCODING_PLAINTEXT);
+        pduRecorder.setVerbose(true); // either sending, receiving or both
+        pduRecorder.start(); // begin running
+    }
+
+    /** All done, release network resources */
+    public void tearDownNetworkInterface()
+    {
+        pduRecorder.stop(); // handles disNetworkInterface.close(), tears down threads and sockets
+    }
+
+    /** 
+     * Send a single Protocol Data Unit (PDU) of any type
+     * @param pdu the pdu to send
+     */
+    protected void sendSinglePdu(Pdu pdu)
+    {
+        try
+        {
+            disNetworkInterface.sendPDU(pdu);
+            Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
+        } 
+        catch (InterruptedException ex)
+        {
+            System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Send Comment PDU
+     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
+     * @param commentType    enumeration value describing purpose of the narrative comment
+     * @param comments       String array of narrative comments
+     */
+    public void sendCommentPdu(VariableRecordType commentType,
+                                     // vararg... variable-length set of String comments can optionally follow
+                                        String... comments)
+    {
+        sendAllPdusForLoopTimestep (null, null, commentType, comments);
+    }
+
+    /**
+     * Send EntityState, Fire, Comment PDUs that got updated for this loop, reflecting state of current simulation timestep.
+     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
+     * @param entityStatePdu the ESPDU to send, if any
+     * @param firePdu        the FirePDU to send, if any
+     * @param commentType    enumeration value describing purpose of the narrative comment
+     * @param comments       String array of narrative comments
+     */
+    public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu,
+                                   FirePdu firePdu,
+                        VariableRecordType commentType,
+                              // vararg... variable-length set of String comments can optionally follow
+                                 String... comments)
+    {
+        if (entityStatePdu != null)
+            sendSinglePdu(entityStatePdu);
+            
+        if (firePdu != null)
+            sendSinglePdu(firePdu); // bang
+        
+        if ((comments != null) && (comments.length > 0))
+        {
+            ArrayList<String> newCommentsList = new ArrayList<>();
+            for (String comment : comments)
+            {
+                if (!comment.isEmpty())
+                {
+                    newCommentsList.add(comment); // OK found something to send
+                }
+            }
+            if (!newCommentsList.isEmpty())
+            {
+                if (commentType == null)
+                    commentType = otherComment; // fallback value otherComment
+                // now build the commentPdu from these string inputs, thus constructing a narrative entry
+                CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments);
+                sendSinglePdu(commentPdu);
+                if (isVerboseComments())
+                {
+                    System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString());
+                    System.out.flush();
+                }
+            }
+        }
+    }
+
+    /**
+     * test for verboseComments mode
+     * @return whether verboseComments mode is enabled
+     */
+    public boolean isVerboseComments() {
+        return verboseComments;
+    }
+
+    /**
+     * set verboseComments mode
+     * @param newVerboseComments whether verboseComments mode is enabled
+     */
+    public void setVerboseComments(boolean newVerboseComments) {
+        this.verboseComments = newVerboseComments;
+    }
+    
+    /**
+     * Initial execution via main() method: handle args array of command-line initialization (CLI) arguments here
+     * @param args command-line parameters: network address and port
+     */
+    protected void handleArgs (String[] args)
+    {
+        // initial execution: handle args array of initialization arguments here
+        if (args.length == 2) 
+        {
+            if ((args[0] != null) && !args[0].isEmpty())
+                thisProgram.setNetworkAddress(args[0]);
+            if ((args[1] != null) && !args[1].isEmpty())
+                thisProgram.setNetworkPort(Integer.parseInt(args[1]));
+        }
+        else if (args.length != 0) 
+        {
+            System.err.println("Usage: " + thisProgram.getClass().getSimpleName() + " [address port]");
+            System.exit(-1);
+        }
+    }
+    
+    /** Locally instantiable copy of program, can be subclassed. */
+    protected static ExampleSimulationProgramHickey thisProgram;
+  
+    /**
+     * Main method is first executed when a program instance is loaded.
+     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
+     * @param args command-line parameters: network address and port.
+     *    Command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
+     */
+    public static void main(String[] args)
+    {
+        TRACE_PREFIX = "[" + ExampleSimulationProgramHickey.class.getName() + "] ";
+        
+        System.out.println(TRACE_PREFIX + "main() started...");
+        
+        thisProgram = new ExampleSimulationProgramHickey(); // creates instance of self within static main() method
+        
+        thisProgram.handleArgs (args); // process command-line invocation arguments
+
+        thisProgram.setUpNetworkInterface();
+        
+//        thisProgram.pduRecorder.setDescriptor (TRACE_PREFIX.replace("[","").replace("]","") + " pduRecorder");
+
+        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
+        
+        thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering
+        
+        System.out.println(TRACE_PREFIX + "complete."); // report successful completion
+        
+        System.exit(0); // ensure all threads and sockets released
+    }
+}
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/PduTrack.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/PduTrack.java
index a64db8a31024a8c286bdc4e193479988da4c8858..00af5fe6fe9232ea8b0497e0a3455de766668670 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/PduTrack.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Hickey/PduTrack.java
@@ -1,1044 +1,1044 @@
-/*
-Copyright (c) 1995-2022 held by the author(s).  All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright
-      notice, this list of conditions and the following disclaimer
-      in the documentation and/or other materials provided with the
-      distribution.
-    * Neither the names of the Naval Postgraduate School (NPS)
-      Modeling Virtual Environments and Simulation (MOVES) Institute
-      https://www.nps.edu and https://www.nps.edu/web/moves
-      nor the names of its contributors may be used to endorse or
-      promote products derived from this software without specific
-      prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-*/
-// TODO move into opendis7 distribution tree in package edu.nps.moves.dis7.utilities.stream;
-
-package MV3500Cohort2022MayJune.homework2.Hickey;
-
-import MV3500Cohort2022MayJune.homework2.Tam.*;
-import MV3500Cohort2022MayJune.homework2.Duran.*;
-import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
-import edu.nps.moves.dis7.enumerations.DisPduType;
-import edu.nps.moves.dis7.enumerations.ForceID;
-import edu.nps.moves.dis7.pdus.EntityID;
-import edu.nps.moves.dis7.pdus.EntityStatePdu;
-import edu.nps.moves.dis7.pdus.EulerAngles;
-import edu.nps.moves.dis7.pdus.Pdu;
-import edu.nps.moves.dis7.pdus.Vector3Double;
-import edu.nps.moves.dis7.utilities.DisTime;
-import edu.nps.moves.dis7.utilities.PduFactory;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
-/**
- * Create a track from DIS ESPDUs
- * @author brutzman
- */
-public class PduTrack
-{   
-    private String          descriptor = new String();
-    private ArrayList<Pdu>  pduList = new ArrayList<>();
-    private EntityStatePdu  initialEspdu;
-    private EntityStatePdu  latestEspdu;
-    private Vector3Double   initialLocation;
-    private Vector3Double   latestLocation;
-    
-    /** waypoint timelineList in seconds */
-    private ArrayList<Float>          timelineList = new ArrayList<>();
-    private ArrayList<Vector3Double> waypointsList = new ArrayList<>();
-    private ArrayList<EulerAngles> eulerAnglesList = new ArrayList<>();
-    
-    private String                             author = new String();
-    private String                 x3dModelIdentifier = new String();
-    private String                 x3dModelName       = "PduTrackInterpolation.x3d";
-    private float             defaultWaypointInterval = -1;
-    private float                     durationSeconds = -1;
-    private String                   x3dTimeSensorDEF = new String();
-    private String         x3dPositionInterpolatorDEF = new String();
-    private String      x3dOrientationInterpolatorDEF = new String();
-    private boolean      addLineBreaksWithinKeyValues = false;
-    /** what kind of timestamp is being used */
-    public    DisTime.TimestampStyle timestampStyle        = DisTime.TimestampStyle.IEEE_ABSOLUTE;
-    /** direct access to pduFactory for creating new PDU instances */
-    protected PduFactory             pduFactory            = new PduFactory(timestampStyle);
-    private   LocalDateTime          recordingStart;
-    private   LocalDateTime          recordingStop;
-    private   String                 todaysDateString      = new String();
-    
-    /** direct access to byteArrayOutputStream */
-    protected ByteArrayOutputStream  byteArrayOutputStream = new ByteArrayOutputStream();
-    /** direct access to DataOutputStream */
-    protected DataOutputStream       dataOutputStream      = new DataOutputStream(byteArrayOutputStream);
-    private   String TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName()) + "] ";
-    
-    /**
-     * Constructor for PduTrack
-     */
-    public PduTrack()
-    {
-        // initialization code here
-        
-        // https://docs.oracle.com/javase/tutorial/datetime/TOC.html
-        // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/package-summary.html
-        // https://stackoverflow.com/questions/5175728/how-to-get-the-current-date-time-in-java/5175900 (scroll down to java.time)
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy");
-        todaysDateString = LocalDate.now().format(formatter);
-//      System.out.println(TRACE_PREFIX + "today=" + todayString);
-    }
-    
-    /**
-     * Set timestampStyle used by PduFactory
-     * @param newTimestampStyle new value to set
-     */
-    public PduTrack(DisTime.TimestampStyle newTimestampStyle)
-    {
-        timestampStyle = newTimestampStyle;
-        DisTime.setTimestampStyle(newTimestampStyle);
-        // automatic super invocation: return PduTrack();
-    }
-    
-    /**
-     * Get simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
-     * @return simple descriptor name
-     */
-    public String getDescriptor() 
-    {
-        return descriptor;
-    }
-    /**
-     * Set new simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
-     * @param newDescriptor simple descriptor name for this interface
-     * @return same object to permit progressive setters */
-    public PduTrack setDescriptor(String newDescriptor) 
-    {
-        if (newDescriptor != null)
-            this.descriptor = newDescriptor.trim();
-        TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName() + " " + descriptor) + "] ";
-        return this;
-    }
-    /**
-     * Get timestampStyle used by PduFactory
-     * @return current timestampStyle
-     */
-    public DisTime.TimestampStyle getTimestampStyle()
-    {
-        return timestampStyle;
-    }
-    /**
-     * Set timestampStyle used by PduFactory
-     * @param newTimestampStyle the timestampStyle to set
-     * @return same object to permit progressive setters 
-     */
-    public PduTrack setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
-    {
-        this.timestampStyle = newTimestampStyle;
-        DisTime.setTimestampStyle(newTimestampStyle);
-        return this;
-    }
-
-    /**
-     * Determine initial location, reset to (0 0 0) if not found
-     * @return current initialLocation
-     */
-    public Vector3Double getInitialLocation() {
-        if (initialLocation == null)
-        {
-            System.out.println (TRACE_PREFIX + "getInitialLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
-            return new Vector3Double();
-        }
-        return initialLocation;
-    }
-    /**
-     * Determine current location, reset to (0 0 0) if not found
-     * @return current latestLocation
-     */
-    public Vector3Double getLatestLocation() {
-        if (latestLocation == null)
-        {
-            System.out.println (TRACE_PREFIX + "getLatestLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
-            return new Vector3Double();
-        }
-        return latestLocation;
-    }
-    /**
-     * Get individual Pdu from pduList, index must not exceed existing pduList size
-     * @param index for pdu of interest
-     * @return pdu of interest
-     */
-    public Pdu getPdu(int index) throws IndexOutOfBoundsException
-    {
-        if ((index >= pduList.size()) || (index < 0))
-        {
-            System.out.println (TRACE_PREFIX + "getPdu(" + index + ") out of bounds, pduList.size()=" + pduList.size());
-            // then throw exception
-        }
-        return pduList.get(index);
-    }
-    /**
-     * get current pduList
-     * @return current pduList
-     */
-    public ArrayList<Pdu> getPduList() {
-        return pduList;
-    }
-    /**
-     * get current waypointsList
-     * @return current waypointsList
-     */
-    public ArrayList<Vector3Double> getWaypointsList() {
-        return waypointsList;
-    }
-    /**
-     * current eulerAnglesList
-     * @return current eulerAnglesList
-     */
-    public ArrayList<EulerAngles> getEulerAnglesList() {
-        return eulerAnglesList;
-    }
-    /**
-     * Time in seconds corresponding to each PDU
-     * @return current timelineList
-     */
-    public ArrayList<Float> getTimelineList() {
-        return timelineList;
-    }
-    /**
-     * Add PDU, typically ESPDU
-     * @param newPdu new Pdu to add, typically ESPDU
-     * @return same object to permit progressive setters
-     */
-    public PduTrack addPdu(Pdu newPdu)
-    {
-        if (newPdu.getPduType() == DisPduType.ENTITY_STATE)
-        {
-////          EntityStatePdu deepCopyEspdu = new EntityStatePdu();
-//            EntityStatePdu deepCopyEspdu = pduFactory.makeEntityStatePdu();
-//            deepCopyEspdu.setTimestamp        (((EntityStatePdu)newPdu).getTimestamp());
-//            deepCopyEspdu.setMarking          (((EntityStatePdu)newPdu).getMarking());
-//            deepCopyEspdu.setEntityID         (((EntityStatePdu)newPdu).getEntityID());
-//            deepCopyEspdu.setForceId          (((EntityStatePdu)newPdu).getForceId());
-//            deepCopyEspdu.setEntityType       (((EntityStatePdu)newPdu).getEntityType());
-//            deepCopyEspdu.setEntityLocation   (((EntityStatePdu)newPdu).getEntityLocation());
-//            deepCopyEspdu.setEntityOrientation(((EntityStatePdu)newPdu).getEntityOrientation());
-            
-            EntityStatePdu deepCopyEspdu = ((EntityStatePdu)newPdu).copy();
-            pduList.add(deepCopyEspdu);
-//          alternative trials:
-//          pduList.add(((EntityStatePdu)newPdu).copyDataOutputStream());
-//          pduList.add(((EntityStatePdu)newPdu).copy());
-            
-            if (initialLocation == null)
-            {
-                initialEspdu    = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
-                initialLocation = deepCopyEspdu.getEntityLocation();
-            }
-            latestEspdu         = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
-            latestLocation      = deepCopyEspdu.getEntityLocation();
-        }
-        else pduList.add(newPdu); // TODO copy() - careful, must be a new object and not a reference
-        return this;
-    }
-    /**
-     * clear all PDUs
-     * @return same object to permit progressive setters
-     */
-    public PduTrack clearPduLists()
-    {
-           getPduList().clear();
-          waypointsList.clear();
-        eulerAnglesList.clear();
-           timelineList.clear();
-           initialEspdu = null;
-            latestEspdu = null;
-        initialLocation = null;
-         latestLocation = null;
-        return this;
-    }
-    
-    private String normalizeNameToken(String candidateDEF)
-    {
-        return candidateDEF.replace(" ", "").replace("-", "")
-                           .replace("(", "").replace(")", "")
-                           .replace("[", "").replace("])", "");
-    }
-
-    /**
-     * Provide DEF value if not defined by program
-     * @return current x3dTimeSensorDEF
-     */
-    public String getX3dTimeSensorDEF() {
-        if    (x3dTimeSensorDEF.isEmpty())
-               x3dTimeSensorDEF = normalizeNameToken(getDescriptor()) + "Clock";
-        return x3dTimeSensorDEF;
-    }
-
-    /**
-     * Set DEF value for X3D node
-     * @param x3dTimeSensorDEF the x3dTimeSensorDEF to set
-     */
-    public void setX3dTimeSensorDEF(String x3dTimeSensorDEF) {
-        this.x3dTimeSensorDEF = normalizeNameToken(x3dTimeSensorDEF);
-    }
-
-    /**
-     * Provide DEF value if not defined by program
-     * @return current x3dPositionInterpolatorDEF
-     */
-    public String getX3dPositionInterpolatorDEF() {
-        if    (x3dPositionInterpolatorDEF.isEmpty())
-               x3dPositionInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Positions";
-        return x3dPositionInterpolatorDEF;
-    }
-
-    /**
-     * Set DEF value for X3D node
-     * @param x3dPositionInterpolatorDEF the x3dPositionInterpolatorDEF to set
-     */
-    public void setX3dPositionInterpolatorDEF(String x3dPositionInterpolatorDEF) {
-        this.x3dPositionInterpolatorDEF = normalizeNameToken(x3dPositionInterpolatorDEF);
-    }
-
-    /**
-     * Provide DEF value if not defined by program
-     * @return current x3dOrientationInterpolatorDEF
-     */
-    public String getX3dOrientationInterpolatorDEF() {
-        if    (x3dOrientationInterpolatorDEF.isEmpty())
-               x3dOrientationInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Orientations";
-        return x3dOrientationInterpolatorDEF;
-    }
-
-    /**
-     * Set DEF value for X3D node
-     * @param x3dOrientationInterpolatorDEF the x3dOrientationInterpolatorDEF to set
-     */
-    public void setX3dOrientationInterpolatorDEF(String x3dOrientationInterpolatorDEF) {
-        this.x3dOrientationInterpolatorDEF = normalizeNameToken(x3dOrientationInterpolatorDEF);
-    }
-
-    /**
-     * Sort all PDUs by timestamp
-     * @see <a href="https://stackoverflow.com/questions/16252269/how-to-sort-an-arraylist">StackOverflow: How to sort an ArrayList?</a>
-     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
-     * @return same object to permit progressive setters
-     */
-    public PduTrack sortPdus()
-    {
-        Collections.sort(pduList, new Comparator<Pdu>() {
-            @Override
-            public int compare(Pdu lhs, Pdu rhs)
-            {
-                // -1 less than, 1 greater than, 0 equal
-                if      (lhs.occursBefore(rhs))
-                         return -1;
-                else if (lhs.occursSameTime(rhs))
-                         return 0;
-                else     return 1;
-            }
-        });
-        return this;
-    }
-    /**
-     * Reverse order of PDU list
-     * @see <a href="https://stackoverflow.com/questions/10766492/what-is-the-simplest-way-to-reverse-an-arraylist">StackOverflow: What is the Simplest Way to Reverse an ArrayList?</a>
-     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
-     * @return same object to permit progressive setters
-     */
-    public PduTrack reversePdus()
-    {
-        Collections.reverse(pduList);
-        return this;
-    }
-
-    /**
-     * Determine whether any ESPDUs have been received by this track
-     * @return whether track is empty
-     */
-    public boolean isTrackEmpty()
-    {
-        return (getEspduCount() == 0);
-    }
-    /**
-     * Count ESPDUs in pduList
-     * @return number of ESPDUs in pduList
-     */
-    public int getEspduCount()
-    {
-        int counter = 0;
-        for (Pdu nextPdu : getPduList())
-        {
-            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
-               counter += 1;
-        }
-        return counter;
-    }
-    /**
-     * Compute track duration in timestamp ticks
-     * @return duration in timestamp ticks between initial and final ESPDU timestamps in waypointList
-     */
-    public int getTotalDurationTicks()
-    {
-        int    initialTime = -1;
-        int      finalTime = -1;
-        int  durationTicks = -1; // used if pduList is empty
-        
-        // must skip through pduList since non-ESPDU PDUs may be present
-        for (Pdu nextPdu : getPduList())
-        {
-            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
-            {
-                if (initialTime == -1)
-                    initialTime = nextPdu.getTimestamp();
-                finalTime = nextPdu.getTimestamp();
-            }
-        }
-        if ((initialTime >= 0) && (finalTime >= 0))
-             durationTicks = (finalTime - initialTime);
-        if (getPduList().isEmpty())
-        {
-            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to empty pdu list");
-        }
-        else if ((durationTicks <= 0) && (defaultWaypointInterval <= 0))
-        {
-            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to illegal pdu list");
-        }
-        return durationTicks;
-    }
-    /**
-     * Compute track duration in seconds
-     * @return duration in seconds between initial and final ESPDU timestamps in waypointList
-     */
-    public float getTotalDurationSeconds()
-    {
-        if (defaultWaypointInterval > 0)
-        {
-            return getEspduCount() * defaultWaypointInterval;
-        }
-        else if (getTotalDurationTicks() < 0)
-               durationSeconds = getTotalDurationTicks() * 1.0f; // TODO convert
-        return durationSeconds;
-    }
-    /**
-     * Create waypoints and angles using all ESPDU points, with no linear regression or array reduction.
-     * @return same object to permit progressive setters
-     */
-    public PduTrack createRawWaypoints()
-    {
-        // https://stackoverflow.com/questions/6536094/java-arraylist-copy
-        
-           timelineList.clear();
-          waypointsList.clear();
-        eulerAnglesList.clear();
-        float clock = 0.0f;
-        for (int i = 0; i < pduList.size(); i++)
-        {
-            Pdu nextPdu = pduList.get(i);
-            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
-            {
-                EntityStatePdu espdu = (EntityStatePdu)nextPdu;
-                if (defaultWaypointInterval > 0)
-                {
-                    timelineList.add(clock);
-                    clock += defaultWaypointInterval;
-                }
-                else
-                {
-                    timelineList.add(espdu.getTimestamp() * 1.0f); // TODO convert
-                }
-                   waypointsList.add(espdu.getEntityLocation());
-                 eulerAnglesList.add(espdu.getEntityOrientation());
-            }
-        }
-        return this;
-    }
-    /** 
-     * Utility method to create TimeSensor
-     * @return TimeSensor string in XML format
-     */
-    public String createX3dTimeSensorString()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("    <TimeSensor");
-        sb.append(" DEF='").append(getX3dTimeSensorDEF()).append("'");
-        sb.append(" cycleInterval='").append(String.valueOf(getTotalDurationSeconds())).append("'");
-        sb.append(" loop='true'");
-        sb.append("/>").append("\n");
-        
-        return sb.toString();
-    }
-    /**
-     * Create PositionInterpolator from Pdu list
-     * @return X3D PositionInterpolator as string
-     */
-    public String createX3dPositionInterpolatorString()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("    <PositionInterpolator");
-        sb.append(" DEF='").append(getX3dPositionInterpolatorDEF()).append("'");
-        sb.append(" key='");
-        for (int i = 0; i < timelineList.size(); i++)
-        {
-            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
-            sb.append(String.valueOf(nextDuration));
-            if (i < timelineList.size() - 1)
-                sb.append(" ");
-        }
-        sb.append("'");
-        sb.append(" keyValue='");
-        for (int i = 0; i < waypointsList.size(); i++)
-        {
-            if (hasAddLineBreaksWithinKeyValues())
-                sb.append("\n");
-            Vector3Double nextPosition = waypointsList.get(i);
-            sb.append(String.valueOf(nextPosition.getX())).append(" ")
-              .append(String.valueOf(nextPosition.getY())).append(" ")
-              .append(String.valueOf(nextPosition.getZ()));
-            if (i < waypointsList.size() - 1)
-                sb.append(",");
-        }
-        sb.append("'");
-        sb.append("/>").append("\n");
-        
-        return sb.toString();
-    }
-    /**
-     * Create OrientationInterpolator from Pdu list
-     * TODO preliminary support only includes horizontal rotation.
-     * @return X3D OrientationInterpolator as string
-     */
-    public String createX3dOrientationInterpolatorString()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("    <OrientationInterpolator");
-        sb.append(" DEF='").append(getX3dOrientationInterpolatorDEF()).append("'");
-        sb.append(" key='");
-        for (int i = 0; i < timelineList.size(); i++)
-        {
-            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
-            sb.append(String.valueOf(nextDuration));
-            if (i < timelineList.size() - 1)
-                sb.append(" ");
-        }
-        sb.append("'");
-        sb.append(" keyValue='");
-        for (int i = 0; i < eulerAnglesList.size(); i++)
-        {
-            if (hasAddLineBreaksWithinKeyValues())
-                sb.append("\n");
-            EulerAngles nextEulerAngle = new EulerAngles();
-            float axisX = 0.0f;
-            float axisY = 1.0f;
-            float axisZ = 0.0f;
-            float angle = 0.0f; // radians
-            
-            nextEulerAngle = eulerAnglesList.get(i);
-            angle = nextEulerAngle.getTheta();
-            
-            sb.append(String.valueOf(axisX)).append(" ")
-              .append(String.valueOf(axisY)).append(" ")
-              .append(String.valueOf(axisZ)).append(" ")
-              .append(String.valueOf(angle));
-            if (i < eulerAnglesList.size() - 1)
-                sb.append(",");
-        }
-        sb.append("'");
-        sb.append("/>").append("\n");
-        
-        return sb.toString();
-    }
-
-    /**
-     * Get name of author used as creator of X3D model
-     * @return current author
-     */
-    public String getAuthor() {
-        return author;
-    }
-
-    /**
-     * Set name of author used as creator of X3D model
-     * @param author the author to set
-     */
-    public void setAuthor(String author) {
-        if  (author == null)
-             author = new String();
-        author = author.trim();
-        this.author = author;
-    }
-
-    /**
-     * Get name of online url identifier for X3D model
-     * @return current x3dModelIdentifier
-     */
-    public String getX3dModelIdentifier() {
-        return x3dModelIdentifier;
-    }
-
-    /**
-     * Set name of online url identifier for X3D model
-     * @param x3dModelIdentifier the x3dModelIdentifier to set
-     */
-    public void setX3dModelIdentifier(String x3dModelIdentifier) {
-        if  (x3dModelIdentifier == null)
-             x3dModelIdentifier = new String();
-        x3dModelIdentifier = x3dModelIdentifier.trim();
-        if (!x3dModelIdentifier.startsWith("http://") && !x3dModelIdentifier.startsWith("https://"))
-            System.out.println(TRACE_PREFIX + "warning, identifier typically begins with https:// or http://");
-        else this.x3dModelIdentifier = x3dModelIdentifier;
-    }
-
-    /**
-     * File name for X3D model production
-     * @return current x3dModelName
-     */
-    public String getX3dModelName() {
-        return x3dModelName;
-    }
-    /**
-     * File name for X3D model production
-     * @param x3dModelName the x3dModelName to set
-     */
-    public void setX3dModelName(String x3dModelName) {
-        if  (x3dModelName == null)
-             x3dModelName = new String();
-        x3dModelName = x3dModelName.trim();
-        this.x3dModelName = x3dModelName;
-    }
-
-    /**
-     * Verbose (but more readable) output of numeric arrays in X3D model
-     * @return current addLineBreaksWithinKeyValues
-     */
-    public boolean hasAddLineBreaksWithinKeyValues() {
-        return addLineBreaksWithinKeyValues;
-    }
-
-    /**
-     * Verbose (but more readable) output of numeric arrays in X3D model
-     * @param addLineBreaksWithinKeyValues the addLineBreaksWithinKeyValues to set
-     */
-    public void setAddLineBreaksWithinKeyValues(boolean addLineBreaksWithinKeyValues) {
-        this.addLineBreaksWithinKeyValues = addLineBreaksWithinKeyValues;
-    }
-    /**
-     * Create full X3D interpolator model from Pdu list, assembling sections of scene graph
-     * @return X3D model as string
-     */
-    public String createX3dModel()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append(createX3dModelHeaderString());
-        sb.append(createX3dTimeSensorString());
-        sb.append(createX3dPositionInterpolatorString());
-        sb.append(createX3dOrientationInterpolatorString());
-        sb.append(createX3dRoutesGeometryFooterString());
-        return sb.toString();
-    }
-    /**
-     * Create PositionInterpolator from Pdu list
-     * @return X3D PositionInterpolator as string
-     */
-    public String createX3dModelHeaderString()
-    {        
-        StringBuilder sb = new StringBuilder();
-        
-        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("\n");
-        sb.append("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 4.0//EN\" \"https://www.web3d.org/specifications/x3d-4.0.dtd\">").append("\n");
-        sb.append("<X3D profile='Interchange' version='4.0' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-4.0.xsd'>").append("\n");
-        sb.append("  <head>").append("\n");
-        if (!getX3dModelName().isEmpty())
-            sb.append("    <meta content='").append(getX3dModelName()).append("' name='title'/>").append("\n");
-        sb.append("    <meta content='Conversion of ESPDU track into X3D animation interpolators and LineSet.' name='description'/>").append("\n");
-        
-        sb.append("    <meta content='1 January 2022' name='created'/>").append("\n");
-        sb.append("    <meta content='").append(todaysDateString).append("' name='modified'/>").append("\n");
-        if (!getAuthor().isEmpty())
-            sb.append("    <meta content='").append(getAuthor()).append("' name='creator'/>").append("\n");
-        if (!getX3dModelIdentifier().isEmpty())
-            sb.append("    <meta content='").append(getX3dModelIdentifier()).append("' name='identifier'/>").append("\n");
-        
-        sb.append("    <meta content='PduTrack utility, opendis7-java Library https://github.com/open-dis/opendis7-java' name='generator'/>").append("\n");
-        sb.append("    <meta content='NPS MOVES MV3500 Networked Graphics https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Resources https://www.web3d.org/x3d/content/examples/X3dResources.html' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Scene Authoring Hints https://www.web3d.org/x3d/content/examples/X3dSceneAuthoringHints.html' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Tooltips https://www.web3d.org/x3d/tooltips/X3dTooltips.html' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Validator https://savage.nps.edu/X3dValidator' name='reference'/>").append("\n");
-        sb.append("    <meta content='Open source https://raw.githubusercontent.com/open-dis/opendis7-java/master/license.html' name='license'/>").append("\n");
-        sb.append("  </head>").append("\n");
-        sb.append("  <Scene>").append("\n");
-        sb.append("    <WorldInfo title='PduTrackInterpolation.x3d'/>").append("\n");
-
-        return sb.toString(); 
-    }
-    /**
-     * Create X3D ROUTEs and footer to connect TimeSensor to interpolators
-     * @return X3D PositionInterpolator as string
-     */
-    public String createX3dRoutesGeometryFooterString()
-    {
-        StringBuilder sb = new StringBuilder();
-        
-        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
-          .append(getX3dTimeSensorDEF())
-          .append("' toField='set_fraction' toNode='")
-          .append(getX3dPositionInterpolatorDEF())
-          .append("'/>").append("\n");
-        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
-          .append(getX3dTimeSensorDEF())
-          .append("' toField='set_fraction' toNode='")
-          .append(getX3dOrientationInterpolatorDEF())
-          .append("'/>").append("\n");
-        
-        sb.append("    <Shape>").append("\n");
-        sb.append("      <Appearance DEF='TrackAppearance'>").append("\n");
-        sb.append("        <Material emissiveColor='0.2 0.8 0.8'/>").append("\n");
-        sb.append("      </Appearance>").append("\n");
-        sb.append("      <LineSet vertexCount='").append(waypointsList.size()).append("'>").append("\n");
-        sb.append("        <Coordinate point='");
-        for (int i = 0; i < waypointsList.size(); i++)
-        {
-            if (hasAddLineBreaksWithinKeyValues())
-                sb.append("\n");
-            Vector3Double nextPosition = waypointsList.get(i);
-            sb.append(String.valueOf(nextPosition.getX())).append(" ")
-              .append(String.valueOf(nextPosition.getY())).append(" ")
-              .append(String.valueOf(nextPosition.getZ()));
-            if (i < waypointsList.size() - 1)
-                sb.append(",");
-        }
-        sb.append("'/>").append("\n");
-        sb.append("      </LineSet>").append("\n");
-        sb.append("    </Shape>").append("\n");
-        sb.append("    <Transform DEF='AnimationTransform'>").append("\n");
-        sb.append("      <Transform rotation='0 0 1 1.57'>").append("\n");
-        sb.append("        <Shape>").append("\n");
-        sb.append("          <Appearance USE='TrackAppearance'/>").append("\n");
-        sb.append("          <Cone bottomRadius='0.5'/>").append("\n");
-        sb.append("        </Shape>").append("\n");
-        sb.append("      </Transform>").append("\n");
-        sb.append("    </Transform>").append("\n");
-        sb.append("    <ROUTE fromField='value_changed' fromNode='")
-          .append(getX3dPositionInterpolatorDEF())
-          .append("' toField='translation' toNode='AnimationTransform'/>").append("\n");
-        sb.append("    <ROUTE fromField='value_changed' fromNode='")
-          .append(getX3dOrientationInterpolatorDEF())
-          .append("' toField='rotation' toNode='AnimationTransform'/>").append("\n");
-        
-        sb.append("  </Scene>").append("\n");
-        sb.append("</X3D>").append("\n");
-        
-        return sb.toString();
-    }
-
-    /**
-     * get defaultWaypointInterval
-     * @return current wayPointInterval
-     */
-    public float getDefaultWaypointInterval() {
-        return defaultWaypointInterval;
-    }
-
-    /**
-     * Set uniform waypoint interval (currently for testing)
-     * @param newWaypointInterval the wayPointInterval to set, in seconds, must be greater than zero
-     * @return same object to permit progressive setters */
-    public PduTrack setDefaultWaypointInterval(float newWaypointInterval) {
-        if (newWaypointInterval > 0.0)
-            this.defaultWaypointInterval = newWaypointInterval;
-        else 
-        {
-            System.out.println(TRACE_PREFIX + "error in setWaypointInterval(newWaypointInterval=" + newWaypointInterval + ") must be greater than zero");
-            return this;
-        }
-            
-        float clock = 0.0f;
-        if (!timelineList.isEmpty())
-        {   
-            ArrayList<Float> newTimelineList = new ArrayList<>();
-            for (int i = 0; i < getEspduCount(); i++)
-            {
-                newTimelineList.add(clock);
-                clock += defaultWaypointInterval;
-            }
-            timelineList = newTimelineList; // TO Array copy?
-        }
-        return this;
-    }
-    /** whether or not to insert commas between hex values */
-    private boolean insertCommas = true;
-    /**
-     * determine whether comma insertion is turned on
-     * @return whether or not to insert commas between hex values
-     */
-    public boolean hasInsertCommas() {
-        return insertCommas;
-    }
-    /**
-     * set whether comma insertion is turned on
-     * @param insertCommas the insertCommas value to set
-     */
-    public void setInsertCommas(boolean insertCommas) {
-        this.insertCommas = insertCommas;
-    }
-    /**
-     * Convert byte array to hex string
-     * @param bytes input data
-     * @param insertCommas whether to insert commas between hex values
-     * @return hex string
-     */
-    public String bytesToHex(byte[] bytes, boolean insertCommas)
-    {
-        this.setInsertCommas(insertCommas);
-        return bytesToHex(bytes);
-    }
-    /**
-     * Convert byte array to hex string
-     * @param bytes input data
-     * @see <a href="https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java">https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java</a>
-     * @return hex string
-     */
-    public static String bytesToHex(byte[] bytes)
-    {
-        boolean insertCommas = true;
-        final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-        char[] hexChars = new char[bytes.length * 2];
-        StringBuilder sb = new StringBuilder();
-        for (int j = 0; j < bytes.length; j++) {
-            int v = bytes[j] & 0xFF;
-            hexChars[j * 2]     = HEX_ARRAY[v >>> 4];
-            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
-//          if (!(hexChars[j * 2] == '0')) // omit leading zero
-            sb.append(hexChars[j * 2]);
-            sb.append(hexChars[j * 2 + 1]);
-            if (insertCommas && (j < bytes.length - 1))
-                sb.append(", ");
-        }
-        return sb.toString();
-    }
-    /**
-     * Report current PDU information to console
-     * @param anEspdu EntityStatePdu of interest
-     * @return same object to permit progressive setters
-     */
-    public PduTrack reportPdu(EntityStatePdu anEspdu)
-    {
-        System.out.println (
-                String.format("%s", anEspdu.getMarkingString().trim()) + ", " +
-                DisTime.convertToString(anEspdu.getTimestamp()) + " (" +
-                String.format("%08d", anEspdu.getTimestamp()) + "), " + 
-                "EntityID=(" + 
-                anEspdu.getEntityID().getSiteID() + ", " +
-                anEspdu.getEntityID().getApplicationID() + ", " + 
-                anEspdu.getEntityID().getEntityID() + "), " +
-                "location=(" + 
-                String.format("%4.1f", anEspdu.getEntityLocation().getX()) + ", " +
-                String.format("%4.1f", anEspdu.getEntityLocation().getY()) + ", " + 
-                String.format("%4.1f", anEspdu.getEntityLocation().getZ()) + ")"
-//              + " " + espdu_1.getEntityLinearVelocity().toString()
-        );
-        return this;
-    }
-    
-    /** Flush all buffers to reduce console scrambling while threaded
-     */
-    protected void flushBuffers()
-    {
-        try
-        {
-                 dataOutputStream.flush(); 
-            byteArrayOutputStream.flush();
-            byteArrayOutputStream.reset();
-            System.err.flush(); 
-            System.out.flush();
-        }
-        catch (IOException ioe)
-        {
-            System.out.println(TRACE_PREFIX + "flushBuffers() IOException: " + ioe.getMessage());
-        }
-    }
-    
-    /** Self test to check basic operation, invoked by main()
-     */
-    @SuppressWarnings("SleepWhileInLoop")
-    public void selfTest()
-    {
-        final int TOTAL_PDUS = 5;
-        System.out.println(TRACE_PREFIX + "selfTest() start...");
-      
-        PduTrack pduTrack = new PduTrack();
-        pduTrack.setDescriptor("PduTrack Self Test");
-        pduTrack.setAuthor("Don Brutzman");
-        pduTrack.setX3dModelIdentifier("https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/PduTrackInterpolation.x3d");
-        pduTrack.setDefaultWaypointInterval(1.0f); // experimentation with timestamp values
-
-        DisTime.setEpochLvcNow();
-        recordingStart = LocalDateTime.now();
-        Instant epoch = DisTime.getEpochLvc();
-        System.out.println(TRACE_PREFIX + "DisTime.hasEpochLvc()=" + DisTime.hasEpochLvc() + 
-                                         ", DisTime.getEpochLvc()=" + epoch + 
-                                         ", Instant.now()=" + Instant.now());
-        
-        EntityID entityID_123 = new EntityID();
-        entityID_123.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
-        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
-
-        for (int i = 0; i < TOTAL_PDUS; i++) // create espdus and add each to track pduList
-        {
-//          EntityStatePdu espdu = new EntityStatePdu();
-            EntityStatePdu espdu = pduFactory.makeEntityStatePdu(); // TODO check Pdu.Type
-            espdu.setTimestamp(DisTime.getCurrentDisTimestamp()); // chooses appropriate version
-            espdu.setMarking("ESPDU " + i);
-            espdu.setEntityLocation(i, i, i);
-            espdu.setEntityOrientation(0, (float)(45.0 * Math.PI / 180.0), 0);
-            espdu.setEntityID(entityID_123);
-            espdu.setForceId(ForceID.FRIENDLY);
-            espdu.setEntityType(new _001Poseidon()); // note import statement above
-            pduTrack.addPdu(espdu); // create copy
-            reportPdu(espdu);
-            try
-            {
-                Thread.sleep(100l);
-            }
-            catch (InterruptedException ie)
-            {
-                System.out.println(TRACE_PREFIX + "exceptional sleep dulay when generating ESPDUs: " + ie.getMessage());
-            }
-        }
-//        System.out.println(TRACE_PREFIX + "reversePdus() then sortPdus() to test track operations");
-//        pduTrack.reversePdus(); // test
-//        pduTrack.sortPdus();    // restore
-        pduTrack.createRawWaypoints(); // copies all ESPDU points to waypoints
-        
-        System.out.println(TRACE_PREFIX + "getEspduCount()="              + pduTrack.getEspduCount());
-        System.out.println(TRACE_PREFIX + "getDefaultWaypointInterval()=" + pduTrack.getDefaultWaypointInterval());
-        System.out.println(TRACE_PREFIX + "getTotalDurationSeconds()="    + pduTrack.getTotalDurationSeconds());
-        
-        System.out.println("=================================");
-        System.out.println("PduTrack pduList marshalling checks");
-        System.out.println("= = = = = = = = = = = = = = = = =");
-        try
-        {
-//          int BYTE_BUFFER_SIZE = 400; // TODO what is expected max buffer size?
-            for (int i = 0; i < TOTAL_PDUS; i++)
-            {
-                Pdu pdu = pduTrack.getPduList().get(i);
-                if (!(pdu instanceof EntityStatePdu))
-                    continue; // skip remainder of this loop
-                EntityStatePdu espdu = (EntityStatePdu) pdu;
-                System.out.println("espdu from pduTrack pduList");
-                reportPdu(espdu);
-                byte[] byteArray = espdu.marshal();
-                System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
-                flushBuffers();
-                
-                ByteBuffer byteBuffer = ByteBuffer.allocate(byteArray.length);
-                espdu.marshal(byteBuffer);
-                System.out.println("espdu.marshal(byteBuffer):                              " + bytesToHex(byteBuffer.array()));
-                flushBuffers();
-                
-                espdu.marshal(dataOutputStream);
-                byte[] byteArrayDOS = byteArrayOutputStream.toByteArray();
-                System.out.println("espdu.marshal(dataOutputStream):                        " + bytesToHex(byteArrayDOS));
-                flushBuffers();
-
-                System.out.println(); // - - - - - - - - - - - - - - - - -
-                
-                System.out.println("espdu.copyByteBuffer()");
-                reportPdu(espdu.copyByteBuffer());
-                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal();
-                System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
-                flushBuffers();
-                
-                ByteBuffer byteBufferCopy = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
-                espdu.copyByteBuffer().marshal(byteBufferCopy);
-                System.out.println("espdu.copyByteBuffer().marshal(byteBufferCopy):         " + bytesToHex(byteBufferCopy.array()));
-                flushBuffers();
-                
-                espdu.copyByteBuffer().marshal(dataOutputStream);
-                byte[] byteArrayDosCopy = byteArrayOutputStream.toByteArray();
-                System.out.println("espdu.copyByteBuffer().marshal(dataOutputStream):       " + bytesToHex(byteArrayDosCopy));
-                flushBuffers();
-
-                System.out.println(); // - - - - - - - - - - - - - - - - -
-                
-                System.out.println("espdu.copyDataOutputStream()");
-                reportPdu(espdu.copyDataOutputStream());
-                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal();
-                System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
-                flushBuffers();
-                
-                ByteBuffer byteBufferCopyDOS = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
-                espdu.copyDataOutputStream().marshal(byteBufferCopyDOS);
-                System.out.println("espdu.copyDataOutputStream().marshal(byteBufferCopy):   " + bytesToHex(byteBufferCopyDOS.array()));
-                flushBuffers();
-                
-                espdu.copyDataOutputStream().marshal(dataOutputStream);
-                byte[] byteArrayDosCopy2 = byteArrayOutputStream.toByteArray();
-                System.out.println("espdu.copyDataOutputStream().marshal(dataOutputStream): " + bytesToHex(byteArrayDosCopy2));
-                flushBuffers();
-                System.out.println();
-                
-                System.out.println("= = = = = = = = = = = = = = = = =");
-            }
-        }
-        catch(Exception e)
-        {
-            System.out.println(TRACE_PREFIX + "Marshalling test exception: " + e.getMessage());
-        }
-        System.out.println("=================================");
-        pduTrack.setAddLineBreaksWithinKeyValues(true);
-        System.out.println(pduTrack.createX3dModel()); // 
-        System.out.println("=================================");
-
-        recordingStop = LocalDateTime.now();
-        System.out.println(TRACE_PREFIX + "selfTest() complete.");
-    }
-    
-    /**
-     * Main method for testing.
-     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
-     * @param args [address, port, descriptor] command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
-     */
-    public static void main(String[] args)
-    {
-        System.out.println("*** PduTrack.main() self test started...");
-          
-        PduTrack pduTrack = new PduTrack();
-        
-        pduTrack.setDescriptor("main() self test");
-        
-        pduTrack.selfTest();
-        
-        System.out.println("*** PduTrack.main() self test complete.");
-    }
-
-}
+/*
+Copyright (c) 1995-2022 held by the author(s).  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer
+      in the documentation and/or other materials provided with the
+      distribution.
+    * Neither the names of the Naval Postgraduate School (NPS)
+      Modeling Virtual Environments and Simulation (MOVES) Institute
+      https://www.nps.edu and https://www.nps.edu/web/moves
+      nor the names of its contributors may be used to endorse or
+      promote products derived from this software without specific
+      prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+// TODO move into opendis7 distribution tree in package edu.nps.moves.dis7.utilities.stream;
+
+package MV3500Cohort2022MayJune.homework2.Hickey;
+
+import MV3500Cohort2022MayJune.homework2.Tam.*;
+import MV3500Cohort2022MayJune.homework2.Duran.*;
+import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
+import edu.nps.moves.dis7.enumerations.DisPduType;
+import edu.nps.moves.dis7.enumerations.ForceID;
+import edu.nps.moves.dis7.pdus.EntityID;
+import edu.nps.moves.dis7.pdus.EntityStatePdu;
+import edu.nps.moves.dis7.pdus.EulerAngles;
+import edu.nps.moves.dis7.pdus.Pdu;
+import edu.nps.moves.dis7.pdus.Vector3Double;
+import edu.nps.moves.dis7.utilities.DisTime;
+import edu.nps.moves.dis7.utilities.PduFactory;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Create a track from DIS ESPDUs
+ * @author brutzman
+ */
+public class PduTrack
+{   
+    private String          descriptor = new String();
+    private ArrayList<Pdu>  pduList = new ArrayList<>();
+    private EntityStatePdu  initialEspdu;
+    private EntityStatePdu  latestEspdu;
+    private Vector3Double   initialLocation;
+    private Vector3Double   latestLocation;
+    
+    /** waypoint timelineList in seconds */
+    private ArrayList<Float>          timelineList = new ArrayList<>();
+    private ArrayList<Vector3Double> waypointsList = new ArrayList<>();
+    private ArrayList<EulerAngles> eulerAnglesList = new ArrayList<>();
+    
+    private String                             author = new String();
+    private String                 x3dModelIdentifier = new String();
+    private String                 x3dModelName       = "PduTrackInterpolation.x3d";
+    private float             defaultWaypointInterval = -1;
+    private float                     durationSeconds = -1;
+    private String                   x3dTimeSensorDEF = new String();
+    private String         x3dPositionInterpolatorDEF = new String();
+    private String      x3dOrientationInterpolatorDEF = new String();
+    private boolean      addLineBreaksWithinKeyValues = false;
+    /** what kind of timestamp is being used */
+    public    DisTime.TimestampStyle timestampStyle        = DisTime.TimestampStyle.IEEE_ABSOLUTE;
+    /** direct access to pduFactory for creating new PDU instances */
+    protected PduFactory             pduFactory            = new PduFactory(timestampStyle);
+    private   LocalDateTime          recordingStart;
+    private   LocalDateTime          recordingStop;
+    private   String                 todaysDateString      = new String();
+    
+    /** direct access to byteArrayOutputStream */
+    protected ByteArrayOutputStream  byteArrayOutputStream = new ByteArrayOutputStream();
+    /** direct access to DataOutputStream */
+    protected DataOutputStream       dataOutputStream      = new DataOutputStream(byteArrayOutputStream);
+    private   String TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName()) + "] ";
+    
+    /**
+     * Constructor for PduTrack
+     */
+    public PduTrack()
+    {
+        // initialization code here
+        
+        // https://docs.oracle.com/javase/tutorial/datetime/TOC.html
+        // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/package-summary.html
+        // https://stackoverflow.com/questions/5175728/how-to-get-the-current-date-time-in-java/5175900 (scroll down to java.time)
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy");
+        todaysDateString = LocalDate.now().format(formatter);
+//      System.out.println(TRACE_PREFIX + "today=" + todayString);
+    }
+    
+    /**
+     * Set timestampStyle used by PduFactory
+     * @param newTimestampStyle new value to set
+     */
+    public PduTrack(DisTime.TimestampStyle newTimestampStyle)
+    {
+        timestampStyle = newTimestampStyle;
+        DisTime.setTimestampStyle(newTimestampStyle);
+        // automatic super invocation: return PduTrack();
+    }
+    
+    /**
+     * Get simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
+     * @return simple descriptor name
+     */
+    public String getDescriptor() 
+    {
+        return descriptor;
+    }
+    /**
+     * Set new simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
+     * @param newDescriptor simple descriptor name for this interface
+     * @return same object to permit progressive setters */
+    public PduTrack setDescriptor(String newDescriptor) 
+    {
+        if (newDescriptor != null)
+            this.descriptor = newDescriptor.trim();
+        TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName() + " " + descriptor) + "] ";
+        return this;
+    }
+    /**
+     * Get timestampStyle used by PduFactory
+     * @return current timestampStyle
+     */
+    public DisTime.TimestampStyle getTimestampStyle()
+    {
+        return timestampStyle;
+    }
+    /**
+     * Set timestampStyle used by PduFactory
+     * @param newTimestampStyle the timestampStyle to set
+     * @return same object to permit progressive setters 
+     */
+    public PduTrack setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
+    {
+        this.timestampStyle = newTimestampStyle;
+        DisTime.setTimestampStyle(newTimestampStyle);
+        return this;
+    }
+
+    /**
+     * Determine initial location, reset to (0 0 0) if not found
+     * @return current initialLocation
+     */
+    public Vector3Double getInitialLocation() {
+        if (initialLocation == null)
+        {
+            System.out.println (TRACE_PREFIX + "getInitialLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
+            return new Vector3Double();
+        }
+        return initialLocation;
+    }
+    /**
+     * Determine current location, reset to (0 0 0) if not found
+     * @return current latestLocation
+     */
+    public Vector3Double getLatestLocation() {
+        if (latestLocation == null)
+        {
+            System.out.println (TRACE_PREFIX + "getLatestLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
+            return new Vector3Double();
+        }
+        return latestLocation;
+    }
+    /**
+     * Get individual Pdu from pduList, index must not exceed existing pduList size
+     * @param index for pdu of interest
+     * @return pdu of interest
+     */
+    public Pdu getPdu(int index) throws IndexOutOfBoundsException
+    {
+        if ((index >= pduList.size()) || (index < 0))
+        {
+            System.out.println (TRACE_PREFIX + "getPdu(" + index + ") out of bounds, pduList.size()=" + pduList.size());
+            // then throw exception
+        }
+        return pduList.get(index);
+    }
+    /**
+     * get current pduList
+     * @return current pduList
+     */
+    public ArrayList<Pdu> getPduList() {
+        return pduList;
+    }
+    /**
+     * get current waypointsList
+     * @return current waypointsList
+     */
+    public ArrayList<Vector3Double> getWaypointsList() {
+        return waypointsList;
+    }
+    /**
+     * current eulerAnglesList
+     * @return current eulerAnglesList
+     */
+    public ArrayList<EulerAngles> getEulerAnglesList() {
+        return eulerAnglesList;
+    }
+    /**
+     * Time in seconds corresponding to each PDU
+     * @return current timelineList
+     */
+    public ArrayList<Float> getTimelineList() {
+        return timelineList;
+    }
+    /**
+     * Add PDU, typically ESPDU
+     * @param newPdu new Pdu to add, typically ESPDU
+     * @return same object to permit progressive setters
+     */
+    public PduTrack addPdu(Pdu newPdu)
+    {
+        if (newPdu.getPduType() == DisPduType.ENTITY_STATE)
+        {
+////          EntityStatePdu deepCopyEspdu = new EntityStatePdu();
+//            EntityStatePdu deepCopyEspdu = pduFactory.makeEntityStatePdu();
+//            deepCopyEspdu.setTimestamp        (((EntityStatePdu)newPdu).getTimestamp());
+//            deepCopyEspdu.setMarking          (((EntityStatePdu)newPdu).getMarking());
+//            deepCopyEspdu.setEntityID         (((EntityStatePdu)newPdu).getEntityID());
+//            deepCopyEspdu.setForceId          (((EntityStatePdu)newPdu).getForceId());
+//            deepCopyEspdu.setEntityType       (((EntityStatePdu)newPdu).getEntityType());
+//            deepCopyEspdu.setEntityLocation   (((EntityStatePdu)newPdu).getEntityLocation());
+//            deepCopyEspdu.setEntityOrientation(((EntityStatePdu)newPdu).getEntityOrientation());
+            
+            EntityStatePdu deepCopyEspdu = ((EntityStatePdu)newPdu).copy();
+            pduList.add(deepCopyEspdu);
+//          alternative trials:
+//          pduList.add(((EntityStatePdu)newPdu).copyDataOutputStream());
+//          pduList.add(((EntityStatePdu)newPdu).copy());
+            
+            if (initialLocation == null)
+            {
+                initialEspdu    = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
+                initialLocation = deepCopyEspdu.getEntityLocation();
+            }
+            latestEspdu         = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
+            latestLocation      = deepCopyEspdu.getEntityLocation();
+        }
+        else pduList.add(newPdu); // TODO copy() - careful, must be a new object and not a reference
+        return this;
+    }
+    /**
+     * clear all PDUs
+     * @return same object to permit progressive setters
+     */
+    public PduTrack clearPduLists()
+    {
+           getPduList().clear();
+          waypointsList.clear();
+        eulerAnglesList.clear();
+           timelineList.clear();
+           initialEspdu = null;
+            latestEspdu = null;
+        initialLocation = null;
+         latestLocation = null;
+        return this;
+    }
+    
+    private String normalizeNameToken(String candidateDEF)
+    {
+        return candidateDEF.replace(" ", "").replace("-", "")
+                           .replace("(", "").replace(")", "")
+                           .replace("[", "").replace("])", "");
+    }
+
+    /**
+     * Provide DEF value if not defined by program
+     * @return current x3dTimeSensorDEF
+     */
+    public String getX3dTimeSensorDEF() {
+        if    (x3dTimeSensorDEF.isEmpty())
+               x3dTimeSensorDEF = normalizeNameToken(getDescriptor()) + "Clock";
+        return x3dTimeSensorDEF;
+    }
+
+    /**
+     * Set DEF value for X3D node
+     * @param x3dTimeSensorDEF the x3dTimeSensorDEF to set
+     */
+    public void setX3dTimeSensorDEF(String x3dTimeSensorDEF) {
+        this.x3dTimeSensorDEF = normalizeNameToken(x3dTimeSensorDEF);
+    }
+
+    /**
+     * Provide DEF value if not defined by program
+     * @return current x3dPositionInterpolatorDEF
+     */
+    public String getX3dPositionInterpolatorDEF() {
+        if    (x3dPositionInterpolatorDEF.isEmpty())
+               x3dPositionInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Positions";
+        return x3dPositionInterpolatorDEF;
+    }
+
+    /**
+     * Set DEF value for X3D node
+     * @param x3dPositionInterpolatorDEF the x3dPositionInterpolatorDEF to set
+     */
+    public void setX3dPositionInterpolatorDEF(String x3dPositionInterpolatorDEF) {
+        this.x3dPositionInterpolatorDEF = normalizeNameToken(x3dPositionInterpolatorDEF);
+    }
+
+    /**
+     * Provide DEF value if not defined by program
+     * @return current x3dOrientationInterpolatorDEF
+     */
+    public String getX3dOrientationInterpolatorDEF() {
+        if    (x3dOrientationInterpolatorDEF.isEmpty())
+               x3dOrientationInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Orientations";
+        return x3dOrientationInterpolatorDEF;
+    }
+
+    /**
+     * Set DEF value for X3D node
+     * @param x3dOrientationInterpolatorDEF the x3dOrientationInterpolatorDEF to set
+     */
+    public void setX3dOrientationInterpolatorDEF(String x3dOrientationInterpolatorDEF) {
+        this.x3dOrientationInterpolatorDEF = normalizeNameToken(x3dOrientationInterpolatorDEF);
+    }
+
+    /**
+     * Sort all PDUs by timestamp
+     * @see <a href="https://stackoverflow.com/questions/16252269/how-to-sort-an-arraylist">StackOverflow: How to sort an ArrayList?</a>
+     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
+     * @return same object to permit progressive setters
+     */
+    public PduTrack sortPdus()
+    {
+        Collections.sort(pduList, new Comparator<Pdu>() {
+            @Override
+            public int compare(Pdu lhs, Pdu rhs)
+            {
+                // -1 less than, 1 greater than, 0 equal
+                if      (lhs.occursBefore(rhs))
+                         return -1;
+                else if (lhs.occursSameTime(rhs))
+                         return 0;
+                else     return 1;
+            }
+        });
+        return this;
+    }
+    /**
+     * Reverse order of PDU list
+     * @see <a href="https://stackoverflow.com/questions/10766492/what-is-the-simplest-way-to-reverse-an-arraylist">StackOverflow: What is the Simplest Way to Reverse an ArrayList?</a>
+     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
+     * @return same object to permit progressive setters
+     */
+    public PduTrack reversePdus()
+    {
+        Collections.reverse(pduList);
+        return this;
+    }
+
+    /**
+     * Determine whether any ESPDUs have been received by this track
+     * @return whether track is empty
+     */
+    public boolean isTrackEmpty()
+    {
+        return (getEspduCount() == 0);
+    }
+    /**
+     * Count ESPDUs in pduList
+     * @return number of ESPDUs in pduList
+     */
+    public int getEspduCount()
+    {
+        int counter = 0;
+        for (Pdu nextPdu : getPduList())
+        {
+            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
+               counter += 1;
+        }
+        return counter;
+    }
+    /**
+     * Compute track duration in timestamp ticks
+     * @return duration in timestamp ticks between initial and final ESPDU timestamps in waypointList
+     */
+    public int getTotalDurationTicks()
+    {
+        int    initialTime = -1;
+        int      finalTime = -1;
+        int  durationTicks = -1; // used if pduList is empty
+        
+        // must skip through pduList since non-ESPDU PDUs may be present
+        for (Pdu nextPdu : getPduList())
+        {
+            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
+            {
+                if (initialTime == -1)
+                    initialTime = nextPdu.getTimestamp();
+                finalTime = nextPdu.getTimestamp();
+            }
+        }
+        if ((initialTime >= 0) && (finalTime >= 0))
+             durationTicks = (finalTime - initialTime);
+        if (getPduList().isEmpty())
+        {
+            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to empty pdu list");
+        }
+        else if ((durationTicks <= 0) && (defaultWaypointInterval <= 0))
+        {
+            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to illegal pdu list");
+        }
+        return durationTicks;
+    }
+    /**
+     * Compute track duration in seconds
+     * @return duration in seconds between initial and final ESPDU timestamps in waypointList
+     */
+    public float getTotalDurationSeconds()
+    {
+        if (defaultWaypointInterval > 0)
+        {
+            return getEspduCount() * defaultWaypointInterval;
+        }
+        else if (getTotalDurationTicks() < 0)
+               durationSeconds = getTotalDurationTicks() * 1.0f; // TODO convert
+        return durationSeconds;
+    }
+    /**
+     * Create waypoints and angles using all ESPDU points, with no linear regression or array reduction.
+     * @return same object to permit progressive setters
+     */
+    public PduTrack createRawWaypoints()
+    {
+        // https://stackoverflow.com/questions/6536094/java-arraylist-copy
+        
+           timelineList.clear();
+          waypointsList.clear();
+        eulerAnglesList.clear();
+        float clock = 0.0f;
+        for (int i = 0; i < pduList.size(); i++)
+        {
+            Pdu nextPdu = pduList.get(i);
+            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
+            {
+                EntityStatePdu espdu = (EntityStatePdu)nextPdu;
+                if (defaultWaypointInterval > 0)
+                {
+                    timelineList.add(clock);
+                    clock += defaultWaypointInterval;
+                }
+                else
+                {
+                    timelineList.add(espdu.getTimestamp() * 1.0f); // TODO convert
+                }
+                   waypointsList.add(espdu.getEntityLocation());
+                 eulerAnglesList.add(espdu.getEntityOrientation());
+            }
+        }
+        return this;
+    }
+    /** 
+     * Utility method to create TimeSensor
+     * @return TimeSensor string in XML format
+     */
+    public String createX3dTimeSensorString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    <TimeSensor");
+        sb.append(" DEF='").append(getX3dTimeSensorDEF()).append("'");
+        sb.append(" cycleInterval='").append(String.valueOf(getTotalDurationSeconds())).append("'");
+        sb.append(" loop='true'");
+        sb.append("/>").append("\n");
+        
+        return sb.toString();
+    }
+    /**
+     * Create PositionInterpolator from Pdu list
+     * @return X3D PositionInterpolator as string
+     */
+    public String createX3dPositionInterpolatorString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    <PositionInterpolator");
+        sb.append(" DEF='").append(getX3dPositionInterpolatorDEF()).append("'");
+        sb.append(" key='");
+        for (int i = 0; i < timelineList.size(); i++)
+        {
+            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
+            sb.append(String.valueOf(nextDuration));
+            if (i < timelineList.size() - 1)
+                sb.append(" ");
+        }
+        sb.append("'");
+        sb.append(" keyValue='");
+        for (int i = 0; i < waypointsList.size(); i++)
+        {
+            if (hasAddLineBreaksWithinKeyValues())
+                sb.append("\n");
+            Vector3Double nextPosition = waypointsList.get(i);
+            sb.append(String.valueOf(nextPosition.getX())).append(" ")
+              .append(String.valueOf(nextPosition.getY())).append(" ")
+              .append(String.valueOf(nextPosition.getZ()));
+            if (i < waypointsList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'");
+        sb.append("/>").append("\n");
+        
+        return sb.toString();
+    }
+    /**
+     * Create OrientationInterpolator from Pdu list
+     * TODO preliminary support only includes horizontal rotation.
+     * @return X3D OrientationInterpolator as string
+     */
+    public String createX3dOrientationInterpolatorString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    <OrientationInterpolator");
+        sb.append(" DEF='").append(getX3dOrientationInterpolatorDEF()).append("'");
+        sb.append(" key='");
+        for (int i = 0; i < timelineList.size(); i++)
+        {
+            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
+            sb.append(String.valueOf(nextDuration));
+            if (i < timelineList.size() - 1)
+                sb.append(" ");
+        }
+        sb.append("'");
+        sb.append(" keyValue='");
+        for (int i = 0; i < eulerAnglesList.size(); i++)
+        {
+            if (hasAddLineBreaksWithinKeyValues())
+                sb.append("\n");
+            EulerAngles nextEulerAngle = new EulerAngles();
+            float axisX = 0.0f;
+            float axisY = 1.0f;
+            float axisZ = 0.0f;
+            float angle = 0.0f; // radians
+            
+            nextEulerAngle = eulerAnglesList.get(i);
+            angle = nextEulerAngle.getTheta();
+            
+            sb.append(String.valueOf(axisX)).append(" ")
+              .append(String.valueOf(axisY)).append(" ")
+              .append(String.valueOf(axisZ)).append(" ")
+              .append(String.valueOf(angle));
+            if (i < eulerAnglesList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'");
+        sb.append("/>").append("\n");
+        
+        return sb.toString();
+    }
+
+    /**
+     * Get name of author used as creator of X3D model
+     * @return current author
+     */
+    public String getAuthor() {
+        return author;
+    }
+
+    /**
+     * Set name of author used as creator of X3D model
+     * @param author the author to set
+     */
+    public void setAuthor(String author) {
+        if  (author == null)
+             author = new String();
+        author = author.trim();
+        this.author = author;
+    }
+
+    /**
+     * Get name of online url identifier for X3D model
+     * @return current x3dModelIdentifier
+     */
+    public String getX3dModelIdentifier() {
+        return x3dModelIdentifier;
+    }
+
+    /**
+     * Set name of online url identifier for X3D model
+     * @param x3dModelIdentifier the x3dModelIdentifier to set
+     */
+    public void setX3dModelIdentifier(String x3dModelIdentifier) {
+        if  (x3dModelIdentifier == null)
+             x3dModelIdentifier = new String();
+        x3dModelIdentifier = x3dModelIdentifier.trim();
+        if (!x3dModelIdentifier.startsWith("http://") && !x3dModelIdentifier.startsWith("https://"))
+            System.out.println(TRACE_PREFIX + "warning, identifier typically begins with https:// or http://");
+        else this.x3dModelIdentifier = x3dModelIdentifier;
+    }
+
+    /**
+     * File name for X3D model production
+     * @return current x3dModelName
+     */
+    public String getX3dModelName() {
+        return x3dModelName;
+    }
+    /**
+     * File name for X3D model production
+     * @param x3dModelName the x3dModelName to set
+     */
+    public void setX3dModelName(String x3dModelName) {
+        if  (x3dModelName == null)
+             x3dModelName = new String();
+        x3dModelName = x3dModelName.trim();
+        this.x3dModelName = x3dModelName;
+    }
+
+    /**
+     * Verbose (but more readable) output of numeric arrays in X3D model
+     * @return current addLineBreaksWithinKeyValues
+     */
+    public boolean hasAddLineBreaksWithinKeyValues() {
+        return addLineBreaksWithinKeyValues;
+    }
+
+    /**
+     * Verbose (but more readable) output of numeric arrays in X3D model
+     * @param addLineBreaksWithinKeyValues the addLineBreaksWithinKeyValues to set
+     */
+    public void setAddLineBreaksWithinKeyValues(boolean addLineBreaksWithinKeyValues) {
+        this.addLineBreaksWithinKeyValues = addLineBreaksWithinKeyValues;
+    }
+    /**
+     * Create full X3D interpolator model from Pdu list, assembling sections of scene graph
+     * @return X3D model as string
+     */
+    public String createX3dModel()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(createX3dModelHeaderString());
+        sb.append(createX3dTimeSensorString());
+        sb.append(createX3dPositionInterpolatorString());
+        sb.append(createX3dOrientationInterpolatorString());
+        sb.append(createX3dRoutesGeometryFooterString());
+        return sb.toString();
+    }
+    /**
+     * Create PositionInterpolator from Pdu list
+     * @return X3D PositionInterpolator as string
+     */
+    public String createX3dModelHeaderString()
+    {        
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("\n");
+        sb.append("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 4.0//EN\" \"https://www.web3d.org/specifications/x3d-4.0.dtd\">").append("\n");
+        sb.append("<X3D profile='Interchange' version='4.0' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-4.0.xsd'>").append("\n");
+        sb.append("  <head>").append("\n");
+        if (!getX3dModelName().isEmpty())
+            sb.append("    <meta content='").append(getX3dModelName()).append("' name='title'/>").append("\n");
+        sb.append("    <meta content='Conversion of ESPDU track into X3D animation interpolators and LineSet.' name='description'/>").append("\n");
+        
+        sb.append("    <meta content='1 January 2022' name='created'/>").append("\n");
+        sb.append("    <meta content='").append(todaysDateString).append("' name='modified'/>").append("\n");
+        if (!getAuthor().isEmpty())
+            sb.append("    <meta content='").append(getAuthor()).append("' name='creator'/>").append("\n");
+        if (!getX3dModelIdentifier().isEmpty())
+            sb.append("    <meta content='").append(getX3dModelIdentifier()).append("' name='identifier'/>").append("\n");
+        
+        sb.append("    <meta content='PduTrack utility, opendis7-java Library https://github.com/open-dis/opendis7-java' name='generator'/>").append("\n");
+        sb.append("    <meta content='NPS MOVES MV3500 Networked Graphics https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Resources https://www.web3d.org/x3d/content/examples/X3dResources.html' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Scene Authoring Hints https://www.web3d.org/x3d/content/examples/X3dSceneAuthoringHints.html' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Tooltips https://www.web3d.org/x3d/tooltips/X3dTooltips.html' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Validator https://savage.nps.edu/X3dValidator' name='reference'/>").append("\n");
+        sb.append("    <meta content='Open source https://raw.githubusercontent.com/open-dis/opendis7-java/master/license.html' name='license'/>").append("\n");
+        sb.append("  </head>").append("\n");
+        sb.append("  <Scene>").append("\n");
+        sb.append("    <WorldInfo title='PduTrackInterpolation.x3d'/>").append("\n");
+
+        return sb.toString(); 
+    }
+    /**
+     * Create X3D ROUTEs and footer to connect TimeSensor to interpolators
+     * @return X3D PositionInterpolator as string
+     */
+    public String createX3dRoutesGeometryFooterString()
+    {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
+          .append(getX3dTimeSensorDEF())
+          .append("' toField='set_fraction' toNode='")
+          .append(getX3dPositionInterpolatorDEF())
+          .append("'/>").append("\n");
+        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
+          .append(getX3dTimeSensorDEF())
+          .append("' toField='set_fraction' toNode='")
+          .append(getX3dOrientationInterpolatorDEF())
+          .append("'/>").append("\n");
+        
+        sb.append("    <Shape>").append("\n");
+        sb.append("      <Appearance DEF='TrackAppearance'>").append("\n");
+        sb.append("        <Material emissiveColor='0.2 0.8 0.8'/>").append("\n");
+        sb.append("      </Appearance>").append("\n");
+        sb.append("      <LineSet vertexCount='").append(waypointsList.size()).append("'>").append("\n");
+        sb.append("        <Coordinate point='");
+        for (int i = 0; i < waypointsList.size(); i++)
+        {
+            if (hasAddLineBreaksWithinKeyValues())
+                sb.append("\n");
+            Vector3Double nextPosition = waypointsList.get(i);
+            sb.append(String.valueOf(nextPosition.getX())).append(" ")
+              .append(String.valueOf(nextPosition.getY())).append(" ")
+              .append(String.valueOf(nextPosition.getZ()));
+            if (i < waypointsList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'/>").append("\n");
+        sb.append("      </LineSet>").append("\n");
+        sb.append("    </Shape>").append("\n");
+        sb.append("    <Transform DEF='AnimationTransform'>").append("\n");
+        sb.append("      <Transform rotation='0 0 1 1.57'>").append("\n");
+        sb.append("        <Shape>").append("\n");
+        sb.append("          <Appearance USE='TrackAppearance'/>").append("\n");
+        sb.append("          <Cone bottomRadius='0.5'/>").append("\n");
+        sb.append("        </Shape>").append("\n");
+        sb.append("      </Transform>").append("\n");
+        sb.append("    </Transform>").append("\n");
+        sb.append("    <ROUTE fromField='value_changed' fromNode='")
+          .append(getX3dPositionInterpolatorDEF())
+          .append("' toField='translation' toNode='AnimationTransform'/>").append("\n");
+        sb.append("    <ROUTE fromField='value_changed' fromNode='")
+          .append(getX3dOrientationInterpolatorDEF())
+          .append("' toField='rotation' toNode='AnimationTransform'/>").append("\n");
+        
+        sb.append("  </Scene>").append("\n");
+        sb.append("</X3D>").append("\n");
+        
+        return sb.toString();
+    }
+
+    /**
+     * get defaultWaypointInterval
+     * @return current wayPointInterval
+     */
+    public float getDefaultWaypointInterval() {
+        return defaultWaypointInterval;
+    }
+
+    /**
+     * Set uniform waypoint interval (currently for testing)
+     * @param newWaypointInterval the wayPointInterval to set, in seconds, must be greater than zero
+     * @return same object to permit progressive setters */
+    public PduTrack setDefaultWaypointInterval(float newWaypointInterval) {
+        if (newWaypointInterval > 0.0)
+            this.defaultWaypointInterval = newWaypointInterval;
+        else 
+        {
+            System.out.println(TRACE_PREFIX + "error in setWaypointInterval(newWaypointInterval=" + newWaypointInterval + ") must be greater than zero");
+            return this;
+        }
+            
+        float clock = 0.0f;
+        if (!timelineList.isEmpty())
+        {   
+            ArrayList<Float> newTimelineList = new ArrayList<>();
+            for (int i = 0; i < getEspduCount(); i++)
+            {
+                newTimelineList.add(clock);
+                clock += defaultWaypointInterval;
+            }
+            timelineList = newTimelineList; // TO Array copy?
+        }
+        return this;
+    }
+    /** whether or not to insert commas between hex values */
+    private boolean insertCommas = true;
+    /**
+     * determine whether comma insertion is turned on
+     * @return whether or not to insert commas between hex values
+     */
+    public boolean hasInsertCommas() {
+        return insertCommas;
+    }
+    /**
+     * set whether comma insertion is turned on
+     * @param insertCommas the insertCommas value to set
+     */
+    public void setInsertCommas(boolean insertCommas) {
+        this.insertCommas = insertCommas;
+    }
+    /**
+     * Convert byte array to hex string
+     * @param bytes input data
+     * @param insertCommas whether to insert commas between hex values
+     * @return hex string
+     */
+    public String bytesToHex(byte[] bytes, boolean insertCommas)
+    {
+        this.setInsertCommas(insertCommas);
+        return bytesToHex(bytes);
+    }
+    /**
+     * Convert byte array to hex string
+     * @param bytes input data
+     * @see <a href="https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java">https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java</a>
+     * @return hex string
+     */
+    public static String bytesToHex(byte[] bytes)
+    {
+        boolean insertCommas = true;
+        final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+        char[] hexChars = new char[bytes.length * 2];
+        StringBuilder sb = new StringBuilder();
+        for (int j = 0; j < bytes.length; j++) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2]     = HEX_ARRAY[v >>> 4];
+            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
+//          if (!(hexChars[j * 2] == '0')) // omit leading zero
+            sb.append(hexChars[j * 2]);
+            sb.append(hexChars[j * 2 + 1]);
+            if (insertCommas && (j < bytes.length - 1))
+                sb.append(", ");
+        }
+        return sb.toString();
+    }
+    /**
+     * Report current PDU information to console
+     * @param anEspdu EntityStatePdu of interest
+     * @return same object to permit progressive setters
+     */
+    public PduTrack reportPdu(EntityStatePdu anEspdu)
+    {
+        System.out.println (
+                String.format("%s", anEspdu.getMarkingString().trim()) + ", " +
+                DisTime.convertToString(anEspdu.getTimestamp()) + " (" +
+                String.format("%08d", anEspdu.getTimestamp()) + "), " + 
+                "EntityID=(" + 
+                anEspdu.getEntityID().getSiteID() + ", " +
+                anEspdu.getEntityID().getApplicationID() + ", " + 
+                anEspdu.getEntityID().getEntityID() + "), " +
+                "location=(" + 
+                String.format("%4.1f", anEspdu.getEntityLocation().getX()) + ", " +
+                String.format("%4.1f", anEspdu.getEntityLocation().getY()) + ", " + 
+                String.format("%4.1f", anEspdu.getEntityLocation().getZ()) + ")"
+//              + " " + espdu_1.getEntityLinearVelocity().toString()
+        );
+        return this;
+    }
+    
+    /** Flush all buffers to reduce console scrambling while threaded
+     */
+    protected void flushBuffers()
+    {
+        try
+        {
+                 dataOutputStream.flush(); 
+            byteArrayOutputStream.flush();
+            byteArrayOutputStream.reset();
+            System.err.flush(); 
+            System.out.flush();
+        }
+        catch (IOException ioe)
+        {
+            System.out.println(TRACE_PREFIX + "flushBuffers() IOException: " + ioe.getMessage());
+        }
+    }
+    
+    /** Self test to check basic operation, invoked by main()
+     */
+    @SuppressWarnings("SleepWhileInLoop")
+    public void selfTest()
+    {
+        final int TOTAL_PDUS = 5;
+        System.out.println(TRACE_PREFIX + "selfTest() start...");
+      
+        PduTrack pduTrack = new PduTrack();
+        pduTrack.setDescriptor("PduTrack Self Test");
+        pduTrack.setAuthor("Don Brutzman");
+        pduTrack.setX3dModelIdentifier("https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/PduTrackInterpolation.x3d");
+        pduTrack.setDefaultWaypointInterval(1.0f); // experimentation with timestamp values
+
+        DisTime.setEpochLvcNow();
+        recordingStart = LocalDateTime.now();
+        Instant epoch = DisTime.getEpochLvc();
+        System.out.println(TRACE_PREFIX + "DisTime.hasEpochLvc()=" + DisTime.hasEpochLvc() + 
+                                         ", DisTime.getEpochLvc()=" + epoch + 
+                                         ", Instant.now()=" + Instant.now());
+        
+        EntityID entityID_123 = new EntityID();
+        entityID_123.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
+        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
+
+        for (int i = 0; i < TOTAL_PDUS; i++) // create espdus and add each to track pduList
+        {
+//          EntityStatePdu espdu = new EntityStatePdu();
+            EntityStatePdu espdu = pduFactory.makeEntityStatePdu(); // TODO check Pdu.Type
+            espdu.setTimestamp(DisTime.getCurrentDisTimestamp()); // chooses appropriate version
+            espdu.setMarking("ESPDU " + i);
+            espdu.setEntityLocation(i, i, i);
+            espdu.setEntityOrientation(0, (float)(45.0 * Math.PI / 180.0), 0);
+            espdu.setEntityID(entityID_123);
+            espdu.setForceId(ForceID.FRIENDLY);
+            espdu.setEntityType(new _001Poseidon()); // note import statement above
+            pduTrack.addPdu(espdu); // create copy
+            reportPdu(espdu);
+            try
+            {
+                Thread.sleep(100l);
+            }
+            catch (InterruptedException ie)
+            {
+                System.out.println(TRACE_PREFIX + "exceptional sleep dulay when generating ESPDUs: " + ie.getMessage());
+            }
+        }
+//        System.out.println(TRACE_PREFIX + "reversePdus() then sortPdus() to test track operations");
+//        pduTrack.reversePdus(); // test
+//        pduTrack.sortPdus();    // restore
+        pduTrack.createRawWaypoints(); // copies all ESPDU points to waypoints
+        
+        System.out.println(TRACE_PREFIX + "getEspduCount()="              + pduTrack.getEspduCount());
+        System.out.println(TRACE_PREFIX + "getDefaultWaypointInterval()=" + pduTrack.getDefaultWaypointInterval());
+        System.out.println(TRACE_PREFIX + "getTotalDurationSeconds()="    + pduTrack.getTotalDurationSeconds());
+        
+        System.out.println("=================================");
+        System.out.println("PduTrack pduList marshalling checks");
+        System.out.println("= = = = = = = = = = = = = = = = =");
+        try
+        {
+//          int BYTE_BUFFER_SIZE = 400; // TODO what is expected max buffer size?
+            for (int i = 0; i < TOTAL_PDUS; i++)
+            {
+                Pdu pdu = pduTrack.getPduList().get(i);
+                if (!(pdu instanceof EntityStatePdu))
+                    continue; // skip remainder of this loop
+                EntityStatePdu espdu = (EntityStatePdu) pdu;
+                System.out.println("espdu from pduTrack pduList");
+                reportPdu(espdu);
+                byte[] byteArray = espdu.marshal().array();
+                System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
+                flushBuffers();
+                
+                ByteBuffer byteBuffer = ByteBuffer.allocate(byteArray.length);
+                espdu.marshal(byteBuffer);
+                System.out.println("espdu.marshal(byteBuffer):                              " + bytesToHex(byteBuffer.array()));
+                flushBuffers();
+                
+                espdu.marshal(dataOutputStream);
+                byte[] byteArrayDOS = byteArrayOutputStream.toByteArray();
+                System.out.println("espdu.marshal(dataOutputStream):                        " + bytesToHex(byteArrayDOS));
+                flushBuffers();
+
+                System.out.println(); // - - - - - - - - - - - - - - - - -
+                
+                System.out.println("espdu.copyByteBuffer()");
+                reportPdu(espdu.copyByteBuffer());
+                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal().array();
+                System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
+                flushBuffers();
+                
+                ByteBuffer byteBufferCopy = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
+                espdu.copyByteBuffer().marshal(byteBufferCopy);
+                System.out.println("espdu.copyByteBuffer().marshal(byteBufferCopy):         " + bytesToHex(byteBufferCopy.array()));
+                flushBuffers();
+                
+                espdu.copyByteBuffer().marshal(dataOutputStream);
+                byte[] byteArrayDosCopy = byteArrayOutputStream.toByteArray();
+                System.out.println("espdu.copyByteBuffer().marshal(dataOutputStream):       " + bytesToHex(byteArrayDosCopy));
+                flushBuffers();
+
+                System.out.println(); // - - - - - - - - - - - - - - - - -
+                
+                System.out.println("espdu.copyDataOutputStream()");
+                reportPdu(espdu.copyDataOutputStream());
+                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal().array();
+                System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
+                flushBuffers();
+                
+                ByteBuffer byteBufferCopyDOS = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
+                espdu.copyDataOutputStream().marshal(byteBufferCopyDOS);
+                System.out.println("espdu.copyDataOutputStream().marshal(byteBufferCopy):   " + bytesToHex(byteBufferCopyDOS.array()));
+                flushBuffers();
+                
+                espdu.copyDataOutputStream().marshal(dataOutputStream);
+                byte[] byteArrayDosCopy2 = byteArrayOutputStream.toByteArray();
+                System.out.println("espdu.copyDataOutputStream().marshal(dataOutputStream): " + bytesToHex(byteArrayDosCopy2));
+                flushBuffers();
+                System.out.println();
+                
+                System.out.println("= = = = = = = = = = = = = = = = =");
+            }
+        }
+        catch(Exception e)
+        {
+            System.out.println(TRACE_PREFIX + "Marshalling test exception: " + e.getMessage());
+        }
+        System.out.println("=================================");
+        pduTrack.setAddLineBreaksWithinKeyValues(true);
+        System.out.println(pduTrack.createX3dModel()); // 
+        System.out.println("=================================");
+
+        recordingStop = LocalDateTime.now();
+        System.out.println(TRACE_PREFIX + "selfTest() complete.");
+    }
+    
+    /**
+     * Main method for testing.
+     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
+     * @param args [address, port, descriptor] command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
+     */
+    public static void main(String[] args)
+    {
+        System.out.println("*** PduTrack.main() self test started...");
+          
+        PduTrack pduTrack = new PduTrack();
+        
+        pduTrack.setDescriptor("main() self test");
+        
+        pduTrack.selfTest();
+        
+        System.out.println("*** PduTrack.main() self test complete.");
+    }
+
+}
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/ExampleSimulationProgramMarks.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/ExampleSimulationProgramMarks.java
index 0a1d79b82dd29525421d938097be39a301e07f79..de4171dc8cff2c0ced5b0a84269bf17dc23b90d6 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/ExampleSimulationProgramMarks.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/ExampleSimulationProgramMarks.java
@@ -364,7 +364,7 @@ public class ExampleSimulationProgramMarks
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/PduTrack.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/PduTrack.java
index 0ebc34e0cb95e875786467b67a499fd4f9ef3482..7891d253427f00358a7160f512978ebced5f2959 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/PduTrack.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Marks/PduTrack.java
@@ -955,7 +955,7 @@ public class PduTrack
                 EntityStatePdu espdu = (EntityStatePdu) pdu;
                 System.out.println("espdu from pduTrack pduList");
                 reportPdu(espdu);
-                byte[] byteArray = espdu.marshal();
+                byte[] byteArray = espdu.marshal().array();
                 System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
                 flushBuffers();
                 
@@ -973,7 +973,7 @@ public class PduTrack
                 
                 System.out.println("espdu.copyByteBuffer()");
                 reportPdu(espdu.copyByteBuffer());
-                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal();
+                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal().array();
                 System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
                 flushBuffers();
                 
@@ -991,7 +991,7 @@ public class PduTrack
                 
                 System.out.println("espdu.copyDataOutputStream()");
                 reportPdu(espdu.copyDataOutputStream());
-                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal();
+                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal().array();
                 System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
                 flushBuffers();
                 
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/ExampleSimulationProgramTam.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/ExampleSimulationProgramTam.java
index a6ab8626861c9b6cebd7655b81eaa4763fdd30d8..49399c2f5d43948a94d2d992700195199fd64160 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/ExampleSimulationProgramTam.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/ExampleSimulationProgramTam.java
@@ -1,504 +1,504 @@
-/**
- * Copyright (c) 2008-2022, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
- * This work is provided under a BSD open-source license, see project license.html and license.txt
- * @author brutzman@nps.edu
- */
-package MV3500Cohort2022MayJune.homework2.Tam;
-
-import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
-import edu.nps.moves.dis7.entities.swe.platform.surface._002Triton;
-import edu.nps.moves.dis7.enumerations.*;
-import edu.nps.moves.dis7.pdus.*;
-import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface;
-import edu.nps.moves.dis7.utilities.DisTime;
-import edu.nps.moves.dis7.utilities.PduFactory;
-import edu.nps.moves.dis7.utilities.SimulationManager;
-import edu.nps.moves.dis7.utilities.stream.PduRecorder;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/** The purpose of this inheritable class is to provide an easily modifiable example simulation program
- *  that includes DIS-capable entities performing tasks of interest, and then reporting activity via PDUs
- *  to the network.
- *  Default program initialization includes PDU recording turned on by default.
- *  @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramLog.txt">ExampleSimulationProgramLog.txt</a>
- */
-public class ExampleSimulationProgramTam
-{
-    private      boolean verboseComments         = true;
-    static final String  NETWORK_ADDRESS_DEFAULT  = "239.1.2.3";
-    static final int     NETWORK_PORT_DEFAULT     = 3000;
-                 String  networkAddress           = NETWORK_ADDRESS_DEFAULT;
-                 int     networkPort              = NETWORK_PORT_DEFAULT;
-                 String  thisHostName             = "localhost";
-                 String  DEFAULT_OUTPUT_DIRECTORY = "./pduLog";
-    
-    /** seconds for real-time execution (not simulation time, which may or may not be the same) */
-    double  currentTimeStep  =  2.0; // seconds
-    /** initial simulation time */
-    double  initialTime = 0.0;
-    /** current simulation time */
-    double  simulationTime;
-
-    /**
-     * Output prefix to help with logging by identifying this class (can be overridden in subclass).
-     */
-    protected static String TRACE_PREFIX;
-    
-    /* Declare DIS Protocol Data Unit (PDU) classes for simulation entities */
-    
-    DisTime.TimestampStyle       timestampStyle      = DisTime.TimestampStyle.IEEE_ABSOLUTE;
-    PduFactory                   pduFactory          = new PduFactory(timestampStyle);
-    
-    /** EntityID settings for entity 1 */
-    protected EntityID           entityID_1          = new EntityID();
-    /** EntityID settings for entity 2 */
-    protected EntityID           entityID_2          = new EntityID();
-    /** ESPDU for entity 1 */
-    protected EntityStatePdu     entityStatePdu_1    = pduFactory.makeEntityStatePdu();
-    /** ESPDU for entity 2 */
-    protected EntityStatePdu     entityStatePdu_2    = pduFactory.makeEntityStatePdu();
-    /** FirePdu for entity 1 first  weapon (if any) */
-    protected FirePdu            firePdu_1a          = pduFactory.makeFirePdu();
-    /** FirePdu for entity 1 second weapon (if any) */
-    protected FirePdu            firePdu_1b          = pduFactory.makeFirePdu();
-    /** MunitionDescriptor for these weapons */
-    protected MunitionDescriptor munitionDescriptor1 = new MunitionDescriptor();
-    
-    /** this class instantiated as an object */
-    SimulationManager        simulationManager = new SimulationManager();
-    
-    /** Get ready, get set... initialize simulation entities
-     */
-    public void initializeSimulationEntities()
-    {
-        // Your model setup: define participants.  who's who in this zoo?
-        // Assuming you keep track of entity objects...  here is some support for for Entity 1.
-        
-        // PDU objects are already created, now set their values.
-        entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
-        
-        entityID_2.setSiteID(1).setApplicationID(2).setEntityID(4); // made-up example ID; 
-        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
-
-        entityStatePdu_1.setEntityID(entityID_1);
-        entityStatePdu_1.setForceId(ForceID.FRIENDLY);
-        entityStatePdu_1.setEntityType(new _001Poseidon()); // note import statement above
-        entityStatePdu_1.setMarking("Entity #1");
-        entityStatePdu_1.getMarkingString(); // check
-
-        entityStatePdu_2.setEntityID(entityID_2);
-        entityStatePdu_2.setForceId(ForceID.OPPOSING);
-        entityStatePdu_2.setEntityType(new _002Triton()); // note import statement above
-        entityStatePdu_2.setMarking("Entity #2");
-
-        // TODO how should we customize this munition?  what is it for your simulation?
-        munitionDescriptor1.setQuantity(1);
-        firePdu_1a.setDescriptor(munitionDescriptor1).setRange(1000.0f);
-        
-        // TODO simulation management PDUs for startup, planning to design special class support 
-//        simulationManager.addEntity();
-        simulationManager.setDescriptor("ExampleSimulationProgram");
-        simulationManager.addHost(thisHostName);
-        simulationManager.setDisThreadedNetworkInterface(disNetworkInterface);
-    }
-                 
-    /**
-     * This runSimulationLoops() method is for you, a customizable programmer-modifiable
-     * code block for defining and running a new simulation of interest.
-     * 
-     * Welcome! Other parts of this program handle bookkeeping and plumbing tasks so that
-     * you can focus on your model entities and activities.
-     * Expandable support includes DIS EntityStatePdu, FirePdu and CommentPdu all available for 
-     * modification and sending in a simulation loop.
-     * Continuous improvement efforts seek to make this program as easy and straightforward
-     * as possible for DIS simulationists to use and adapt.
-     * All of the other methods are setup, teardown and configuration that you may find
-     * interesting, even helpful, but don't really have to worry about.
-     */
-    @SuppressWarnings("SleepWhileInLoop") // yes we do that
-    public void runSimulationLoops ()
-    {
-      try
-      {              
-        final int     SIMULATION_MAX_LOOP_COUNT = 10; // be deliberate out there!  also avoid infinite loops.
-              int     simulationLoopCount = 0;        // variable, initialized at 0
-              boolean simulationComplete = false;     // sentinel variable as termination condition, are we done yet?
-        
-        // TODO reset Clock Time to today's date and timestamp to zero, providing consistent outputs for each simulation run
-
-        pduRecorder.setVerbose(true);
-        
-        initializeSimulationEntities();
-        
-        simulationManager.simulationJoin();
-        simulationManager.simulationStart();
-        
-        // ===================================================================================================
-        // loop the simulation while allowed, programmer can set additional conditions to break out and finish
-        while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT)  // are we done yet?
-        {
-            simulationLoopCount++; // good practice: increment loop counter as first action in that loop
-            
-            // =============================================================================================
-            // * your own simulation code starts here! *
-            // =============================================================================================
-            
-            //  are there any other variables to modify at the beginning of your loop?
-            
-            // compute a track, update an ESPDU, whatever it is that your model is doing...
-            
-            // Where is my entity?  Insert changes in position; this sample only changes X position.
-            entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() + 1.0); // 1m per timestep
-            
-            // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoot at!
-            
-            // etc. etc. your code goes here for your simulation of interest
-                
-            // something happens between my simulation entities, la de da de da...
-            System.out.println ("... My simulation just did something, no really...");
-            System.out.flush();
-            
-            
-            // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
-            narrativeMessage1 = "MV3500 ExampleSimulationProgram";
-            narrativeMessage2 = "runSimulation() loop " + simulationLoopCount;
-            narrativeMessage3 = ""; // intentionally blank for testing
-
-            // your loop termination condition goes here
-            if (simulationLoopCount > 4) // for example
-            {
-                simulationComplete = true;
-            }      
-            // =============================================================================================
-            // * your own simulation code is finished here! *
-            // =============================================================================================
-            
-            // staying synchronized with timestep: wait duration for elapsed time in this loop
-            // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
-            Thread.sleep((long)(currentTimeStep * 1000)); // seconds * (1000 msec/sec) = milliseconds
-            System.out.println ("... [Pausing for " + currentTimeStep + " seconds]");
-            
-            // OK now send the status PDUs for this loop, and then continue
-            System.out.println ("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
-            System.out.flush();
-            sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu_1a, currentTimeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
-            sendSinglePdu(entityStatePdu_2); // me too i.e. 2!
-            System.out.println ("... [PDUs successfully sent for this loop]");
-            System.out.flush();
-            
-            // ===============================
-            // current loop now finished, check whether to terminate if simulation complete, otherwise continue
-            if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
-            {
-                System.out.println ("... [loop termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + 
-                System.out.flush();
-                break;
-            }
-        }   // end of simulation loop
-        // ===================================================================================================
-
-        narrativeMessage2 = "runSimulation() completed successfully"; // all done
-        sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
-        System.out.println ("... [final CommentPdu successfully sent for simulation]");
-        
-        // TODO simulation management PDUs
-        simulationManager.simulationStop();
-        simulationManager.simulationLeave();
-      } 
-      catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
-      {
-        Logger.getLogger(ExampleSimulationProgramTam.class.getName()).log(Level.SEVERE, null, iex);
-      }
-    }
-    /* **************************** infrastructure code, modification is seldom needed ************************* */
-                 
-    String narrativeMessage1 = new String();
-    String narrativeMessage2 = new String();
-    String narrativeMessage3 = new String();
-          
-    /* VariableRecordType enumerations have potential use with CommentPdu logs */
-    /* TODO contrast to EntityType */
-    VariableRecordType     descriptionComment = VariableRecordType.DESCRIPTION;
-    VariableRecordType       narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT;
-    VariableRecordType          statusComment = VariableRecordType.APPLICATION_STATUS;
-    VariableRecordType currentTimeStepComment = VariableRecordType.APPLICATION_TIMESTEP;
-    VariableRecordType           otherComment = VariableRecordType.OTHER;
-    
-    // class variables
-    DisThreadedNetworkInterface             disNetworkInterface;
-    DisThreadedNetworkInterface.PduListener pduListener;
-    Pdu                                     receivedPdu;
-    PduRecorder                             pduRecorder;
-    
-    /**
-     * Constructor design goal: additional built-in initialization conveniences can go here
-     * to keep student efforts focused on the runSimulation() method.
-     */
-    public ExampleSimulationProgramTam()
-    {
-        DisTime.setTimestampStyle(timestampStyle);
-        
-        try
-        {
-            thisHostName = InetAddress.getLocalHost().getHostName();
-            System.out.println(TRACE_PREFIX + "thisHostName=" + thisHostName);
-        }
-        catch (UnknownHostException uhe)
-        {
-            System.out.println(TRACE_PREFIX + thisHostName + "not connected to network: " + uhe.getMessage());
-        }
-    }
-    
-    /**
-     * Utility Constructor that allows your example simulation program to override default network address and port
-     * @param address network address to use
-     * @param port corresponding network port to use
-     */
-    public ExampleSimulationProgramTam(String address, int port)
-    {
-        super();
-        
-        setNetworkAddress(address);
-        
-        setNetworkPort(port);
-    }
-
-    /**
-     * get current networkAddress as a string
-     * @return the networkAddress
-     */
-    public String getNetworkAddress()
-    {
-        return networkAddress;
-    }
-    /**
-     * set current networkAddress using a string
-     * @param newNetworkAddress the networkAddress to set
-     */
-    public final void setNetworkAddress(String newNetworkAddress)
-    {
-        this.networkAddress = newNetworkAddress;
-    }
-
-    /**
-     * get current networkPort
-     * @return the networkPort
-     */
-    public int getNetworkPort()
-    {
-        return networkPort;
-    }
-    /**
-     * set current networkPort
-     * @param newNetworkPort the networkPort to set
-     */
-    public final void setNetworkPort(int newNetworkPort)
-    {
-        this.networkPort = newNetworkPort;
-    }
-    /**
-     * Get timestampStyle used by PduFactory
-     * @return current timestampStyle
-     */
-    public DisTime.TimestampStyle getTimestampStyle()
-    {
-        return timestampStyle;
-    }
-    /**
-     * Set timestampStyle used by PduFactory
-     * @param newTimestampStyle the timestampStyle to set
-     * @return same object to permit progressive setters 
-     */
-    public ExampleSimulationProgramTam setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
-    {
-        timestampStyle = newTimestampStyle;
-        DisTime.setTimestampStyle(newTimestampStyle);
-        return this;
-    }
-
-    /**
-     * Initialize network interface, choosing best available network interface
-     */
-    public void setUpNetworkInterface()
-    {
-        disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort());
-        disNetworkInterface.setDescriptor ("ExampleSimulationProgram pdu looping");
-        
-        System.out.println("Network confirmation:" +
-               " address=" + disNetworkInterface.getAddress()+ //  disNetworkInterface.getMulticastGroup() + 
-                  " port=" + disNetworkInterface.getPort());   // + disNetworkInterface.getDisPort());
-        pduListener = new DisThreadedNetworkInterface.PduListener()
-        {
-            /** Callback handler for listener */
-            @Override
-            public void incomingPdu(Pdu newPdu)
-            {
-                receivedPdu = newPdu;
-            }
-        };
-        disNetworkInterface.addListener(pduListener);
-        
-        String outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
-        System.out.println("Beginning pdu save to directory " + outputDirectory);
-        pduRecorder = new PduRecorder(outputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save
-        pduRecorder.setEncodingPduLog(PduRecorder.ENCODING_PLAINTEXT);
-        pduRecorder.setVerbose(true); // either sending, receiving or both
-        pduRecorder.start(); // begin running
-    }
-
-    /** All done, release network resources */
-    public void tearDownNetworkInterface()
-    {
-        pduRecorder.stop(); // handles disNetworkInterface.close(), tears down threads and sockets
-    }
-
-    /** 
-     * Send a single Protocol Data Unit (PDU) of any type
-     * @param pdu the pdu to send
-     */
-    protected void sendSinglePdu(Pdu pdu)
-    {
-        try
-        {
-            disNetworkInterface.send(pdu);
-            Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
-        } 
-        catch (InterruptedException ex)
-        {
-            System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
-            System.exit(1);
-        }
-    }
-
-    /**
-     * Send Comment PDU
-     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
-     * @param commentType    enumeration value describing purpose of the narrative comment
-     * @param comments       String array of narrative comments
-     */
-    public void sendCommentPdu(VariableRecordType commentType,
-                                     // vararg... variable-length set of String comments can optionally follow
-                                        String... comments)
-    {
-        sendAllPdusForLoopTimestep (null, null, commentType, comments);
-    }
-
-    /**
-     * Send EntityState, Fire, Comment PDUs that got updated for this loop, reflecting state of current simulation timestep.
-     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
-     * @param entityStatePdu the ESPDU to send, if any
-     * @param firePdu        the FirePDU to send, if any
-     * @param commentType    enumeration value describing purpose of the narrative comment
-     * @param comments       String array of narrative comments
-     */
-    public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu,
-                                   FirePdu firePdu,
-                        VariableRecordType commentType,
-                              // vararg... variable-length set of String comments can optionally follow
-                                 String... comments)
-    {
-        if (entityStatePdu != null)
-            sendSinglePdu(entityStatePdu);
-            
-        if (firePdu != null)
-            sendSinglePdu(firePdu); // bang
-        
-        if ((comments != null) && (comments.length > 0))
-        {
-            ArrayList<String> newCommentsList = new ArrayList<>();
-            for (String comment : comments)
-            {
-                if (!comment.isEmpty())
-                {
-                    newCommentsList.add(comment); // OK found something to send
-                }
-            }
-            if (!newCommentsList.isEmpty())
-            {
-                if (commentType == null)
-                    commentType = otherComment; // fallback value otherComment
-                // now build the commentPdu from these string inputs, thus constructing a narrative entry
-                CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments);
-                sendSinglePdu(commentPdu);
-                if (isVerboseComments())
-                {
-                    System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString());
-                    System.out.flush();
-                }
-            }
-        }
-    }
-
-    /**
-     * test for verboseComments mode
-     * @return whether verboseComments mode is enabled
-     */
-    public boolean isVerboseComments() {
-        return verboseComments;
-    }
-
-    /**
-     * set verboseComments mode
-     * @param newVerboseComments whether verboseComments mode is enabled
-     */
-    public void setVerboseComments(boolean newVerboseComments) {
-        this.verboseComments = newVerboseComments;
-    }
-    
-    /**
-     * Initial execution via main() method: handle args array of command-line initialization (CLI) arguments here
-     * @param args command-line parameters: network address and port
-     */
-    protected void handleArgs (String[] args)
-    {
-        // initial execution: handle args array of initialization arguments here
-        if (args.length == 2) 
-        {
-            if ((args[0] != null) && !args[0].isEmpty())
-                thisProgram.setNetworkAddress(args[0]);
-            if ((args[1] != null) && !args[1].isEmpty())
-                thisProgram.setNetworkPort(Integer.parseInt(args[1]));
-        }
-        else if (args.length != 0) 
-        {
-            System.err.println("Usage: " + thisProgram.getClass().getSimpleName() + " [address port]");
-            System.exit(-1);
-        }
-    }
-    
-    /** Locally instantiable copy of program, can be subclassed. */
-    protected static ExampleSimulationProgramTam thisProgram;
-  
-    /**
-     * Main method is first executed when a program instance is loaded.
-     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
-     * @param args command-line parameters: network address and port.
-     *    Command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
-     */
-    public static void main(String[] args)
-    {
-        TRACE_PREFIX = "[" + ExampleSimulationProgramTam.class.getName() + "] ";
-        
-        System.out.println(TRACE_PREFIX + "main() started...");
-        
-        thisProgram = new ExampleSimulationProgramTam(); // creates instance of self within static main() method
-        
-        thisProgram.handleArgs (args); // process command-line invocation arguments
-
-        thisProgram.setUpNetworkInterface();
-        
-//        thisProgram.pduRecorder.setDescriptor (TRACE_PREFIX.replace("[","").replace("]","") + " pduRecorder");
-
-        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
-        
-        thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering
-        
-        System.out.println(TRACE_PREFIX + "complete."); // report successful completion
-        
-        System.exit(0); // ensure all threads and sockets released
-    }
-}
+/**
+ * Copyright (c) 2008-2022, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
+ * This work is provided under a BSD open-source license, see project license.html and license.txt
+ * @author brutzman@nps.edu
+ */
+package MV3500Cohort2022MayJune.homework2.Tam;
+
+import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
+import edu.nps.moves.dis7.entities.swe.platform.surface._002Triton;
+import edu.nps.moves.dis7.enumerations.*;
+import edu.nps.moves.dis7.pdus.*;
+import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface;
+import edu.nps.moves.dis7.utilities.DisTime;
+import edu.nps.moves.dis7.utilities.PduFactory;
+import edu.nps.moves.dis7.utilities.SimulationManager;
+import edu.nps.moves.dis7.utilities.stream.PduRecorder;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/** The purpose of this inheritable class is to provide an easily modifiable example simulation program
+ *  that includes DIS-capable entities performing tasks of interest, and then reporting activity via PDUs
+ *  to the network.
+ *  Default program initialization includes PDU recording turned on by default.
+ *  @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramLog.txt">ExampleSimulationProgramLog.txt</a>
+ */
+public class ExampleSimulationProgramTam
+{
+    private      boolean verboseComments         = true;
+    static final String  NETWORK_ADDRESS_DEFAULT  = "239.1.2.3";
+    static final int     NETWORK_PORT_DEFAULT     = 3000;
+                 String  networkAddress           = NETWORK_ADDRESS_DEFAULT;
+                 int     networkPort              = NETWORK_PORT_DEFAULT;
+                 String  thisHostName             = "localhost";
+                 String  DEFAULT_OUTPUT_DIRECTORY = "./pduLog";
+    
+    /** seconds for real-time execution (not simulation time, which may or may not be the same) */
+    double  currentTimeStep  =  2.0; // seconds
+    /** initial simulation time */
+    double  initialTime = 0.0;
+    /** current simulation time */
+    double  simulationTime;
+
+    /**
+     * Output prefix to help with logging by identifying this class (can be overridden in subclass).
+     */
+    protected static String TRACE_PREFIX;
+    
+    /* Declare DIS Protocol Data Unit (PDU) classes for simulation entities */
+    
+    DisTime.TimestampStyle       timestampStyle      = DisTime.TimestampStyle.IEEE_ABSOLUTE;
+    PduFactory                   pduFactory          = new PduFactory(timestampStyle);
+    
+    /** EntityID settings for entity 1 */
+    protected EntityID           entityID_1          = new EntityID();
+    /** EntityID settings for entity 2 */
+    protected EntityID           entityID_2          = new EntityID();
+    /** ESPDU for entity 1 */
+    protected EntityStatePdu     entityStatePdu_1    = pduFactory.makeEntityStatePdu();
+    /** ESPDU for entity 2 */
+    protected EntityStatePdu     entityStatePdu_2    = pduFactory.makeEntityStatePdu();
+    /** FirePdu for entity 1 first  weapon (if any) */
+    protected FirePdu            firePdu_1a          = pduFactory.makeFirePdu();
+    /** FirePdu for entity 1 second weapon (if any) */
+    protected FirePdu            firePdu_1b          = pduFactory.makeFirePdu();
+    /** MunitionDescriptor for these weapons */
+    protected MunitionDescriptor munitionDescriptor1 = new MunitionDescriptor();
+    
+    /** this class instantiated as an object */
+    SimulationManager        simulationManager = new SimulationManager();
+    
+    /** Get ready, get set... initialize simulation entities
+     */
+    public void initializeSimulationEntities()
+    {
+        // Your model setup: define participants.  who's who in this zoo?
+        // Assuming you keep track of entity objects...  here is some support for for Entity 1.
+        
+        // PDU objects are already created, now set their values.
+        entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
+        
+        entityID_2.setSiteID(1).setApplicationID(2).setEntityID(4); // made-up example ID; 
+        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
+
+        entityStatePdu_1.setEntityID(entityID_1);
+        entityStatePdu_1.setForceId(ForceID.FRIENDLY);
+        entityStatePdu_1.setEntityType(new _001Poseidon()); // note import statement above
+        entityStatePdu_1.setMarking("Entity #1");
+        entityStatePdu_1.getMarkingString(); // check
+
+        entityStatePdu_2.setEntityID(entityID_2);
+        entityStatePdu_2.setForceId(ForceID.OPPOSING);
+        entityStatePdu_2.setEntityType(new _002Triton()); // note import statement above
+        entityStatePdu_2.setMarking("Entity #2");
+
+        // TODO how should we customize this munition?  what is it for your simulation?
+        munitionDescriptor1.setQuantity(1);
+        firePdu_1a.setDescriptor(munitionDescriptor1).setRange(1000.0f);
+        
+        // TODO simulation management PDUs for startup, planning to design special class support 
+//        simulationManager.addEntity();
+        simulationManager.setDescriptor("ExampleSimulationProgram");
+        simulationManager.addHost(thisHostName);
+        simulationManager.setDisThreadedNetworkInterface(disNetworkInterface);
+    }
+                 
+    /**
+     * This runSimulationLoops() method is for you, a customizable programmer-modifiable
+     * code block for defining and running a new simulation of interest.
+     * 
+     * Welcome! Other parts of this program handle bookkeeping and plumbing tasks so that
+     * you can focus on your model entities and activities.
+     * Expandable support includes DIS EntityStatePdu, FirePdu and CommentPdu all available for 
+     * modification and sending in a simulation loop.
+     * Continuous improvement efforts seek to make this program as easy and straightforward
+     * as possible for DIS simulationists to use and adapt.
+     * All of the other methods are setup, teardown and configuration that you may find
+     * interesting, even helpful, but don't really have to worry about.
+     */
+    @SuppressWarnings("SleepWhileInLoop") // yes we do that
+    public void runSimulationLoops ()
+    {
+      try
+      {              
+        final int     SIMULATION_MAX_LOOP_COUNT = 10; // be deliberate out there!  also avoid infinite loops.
+              int     simulationLoopCount = 0;        // variable, initialized at 0
+              boolean simulationComplete = false;     // sentinel variable as termination condition, are we done yet?
+        
+        // TODO reset Clock Time to today's date and timestamp to zero, providing consistent outputs for each simulation run
+
+        pduRecorder.setVerbose(true);
+        
+        initializeSimulationEntities();
+        
+        simulationManager.simulationJoin();
+        simulationManager.simulationStart();
+        
+        // ===================================================================================================
+        // loop the simulation while allowed, programmer can set additional conditions to break out and finish
+        while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT)  // are we done yet?
+        {
+            simulationLoopCount++; // good practice: increment loop counter as first action in that loop
+            
+            // =============================================================================================
+            // * your own simulation code starts here! *
+            // =============================================================================================
+            
+            //  are there any other variables to modify at the beginning of your loop?
+            
+            // compute a track, update an ESPDU, whatever it is that your model is doing...
+            
+            // Where is my entity?  Insert changes in position; this sample only changes X position.
+            entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX() + 1.0); // 1m per timestep
+            
+            // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoot at!
+            
+            // etc. etc. your code goes here for your simulation of interest
+                
+            // something happens between my simulation entities, la de da de da...
+            System.out.println ("... My simulation just did something, no really...");
+            System.out.flush();
+            
+            
+            // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
+            narrativeMessage1 = "MV3500 ExampleSimulationProgram";
+            narrativeMessage2 = "runSimulation() loop " + simulationLoopCount;
+            narrativeMessage3 = ""; // intentionally blank for testing
+
+            // your loop termination condition goes here
+            if (simulationLoopCount > 4) // for example
+            {
+                simulationComplete = true;
+            }      
+            // =============================================================================================
+            // * your own simulation code is finished here! *
+            // =============================================================================================
+            
+            // staying synchronized with timestep: wait duration for elapsed time in this loop
+            // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
+            Thread.sleep((long)(currentTimeStep * 1000)); // seconds * (1000 msec/sec) = milliseconds
+            System.out.println ("... [Pausing for " + currentTimeStep + " seconds]");
+            
+            // OK now send the status PDUs for this loop, and then continue
+            System.out.println ("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
+            System.out.flush();
+            sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu_1a, currentTimeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
+            sendSinglePdu(entityStatePdu_2); // me too i.e. 2!
+            System.out.println ("... [PDUs successfully sent for this loop]");
+            System.out.flush();
+            
+            // ===============================
+            // current loop now finished, check whether to terminate if simulation complete, otherwise continue
+            if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
+            {
+                System.out.println ("... [loop termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + 
+                System.out.flush();
+                break;
+            }
+        }   // end of simulation loop
+        // ===================================================================================================
+
+        narrativeMessage2 = "runSimulation() completed successfully"; // all done
+        sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
+        System.out.println ("... [final CommentPdu successfully sent for simulation]");
+        
+        // TODO simulation management PDUs
+        simulationManager.simulationStop();
+        simulationManager.simulationLeave();
+      } 
+      catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
+      {
+        Logger.getLogger(ExampleSimulationProgramTam.class.getName()).log(Level.SEVERE, null, iex);
+      }
+    }
+    /* **************************** infrastructure code, modification is seldom needed ************************* */
+                 
+    String narrativeMessage1 = new String();
+    String narrativeMessage2 = new String();
+    String narrativeMessage3 = new String();
+          
+    /* VariableRecordType enumerations have potential use with CommentPdu logs */
+    /* TODO contrast to EntityType */
+    VariableRecordType     descriptionComment = VariableRecordType.DESCRIPTION;
+    VariableRecordType       narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT;
+    VariableRecordType          statusComment = VariableRecordType.APPLICATION_STATUS;
+    VariableRecordType currentTimeStepComment = VariableRecordType.APPLICATION_TIMESTEP;
+    VariableRecordType           otherComment = VariableRecordType.OTHER;
+    
+    // class variables
+    DisThreadedNetworkInterface             disNetworkInterface;
+    DisThreadedNetworkInterface.PduListener pduListener;
+    Pdu                                     receivedPdu;
+    PduRecorder                             pduRecorder;
+    
+    /**
+     * Constructor design goal: additional built-in initialization conveniences can go here
+     * to keep student efforts focused on the runSimulation() method.
+     */
+    public ExampleSimulationProgramTam()
+    {
+        DisTime.setTimestampStyle(timestampStyle);
+        
+        try
+        {
+            thisHostName = InetAddress.getLocalHost().getHostName();
+            System.out.println(TRACE_PREFIX + "thisHostName=" + thisHostName);
+        }
+        catch (UnknownHostException uhe)
+        {
+            System.out.println(TRACE_PREFIX + thisHostName + "not connected to network: " + uhe.getMessage());
+        }
+    }
+    
+    /**
+     * Utility Constructor that allows your example simulation program to override default network address and port
+     * @param address network address to use
+     * @param port corresponding network port to use
+     */
+    public ExampleSimulationProgramTam(String address, int port)
+    {
+        super();
+        
+        setNetworkAddress(address);
+        
+        setNetworkPort(port);
+    }
+
+    /**
+     * get current networkAddress as a string
+     * @return the networkAddress
+     */
+    public String getNetworkAddress()
+    {
+        return networkAddress;
+    }
+    /**
+     * set current networkAddress using a string
+     * @param newNetworkAddress the networkAddress to set
+     */
+    public final void setNetworkAddress(String newNetworkAddress)
+    {
+        this.networkAddress = newNetworkAddress;
+    }
+
+    /**
+     * get current networkPort
+     * @return the networkPort
+     */
+    public int getNetworkPort()
+    {
+        return networkPort;
+    }
+    /**
+     * set current networkPort
+     * @param newNetworkPort the networkPort to set
+     */
+    public final void setNetworkPort(int newNetworkPort)
+    {
+        this.networkPort = newNetworkPort;
+    }
+    /**
+     * Get timestampStyle used by PduFactory
+     * @return current timestampStyle
+     */
+    public DisTime.TimestampStyle getTimestampStyle()
+    {
+        return timestampStyle;
+    }
+    /**
+     * Set timestampStyle used by PduFactory
+     * @param newTimestampStyle the timestampStyle to set
+     * @return same object to permit progressive setters 
+     */
+    public ExampleSimulationProgramTam setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
+    {
+        timestampStyle = newTimestampStyle;
+        DisTime.setTimestampStyle(newTimestampStyle);
+        return this;
+    }
+
+    /**
+     * Initialize network interface, choosing best available network interface
+     */
+    public void setUpNetworkInterface()
+    {
+        disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort());
+        disNetworkInterface.setDescriptor ("ExampleSimulationProgram pdu looping");
+        
+        System.out.println("Network confirmation:" +
+               " address=" + disNetworkInterface.getAddress()+ //  disNetworkInterface.getMulticastGroup() + 
+                  " port=" + disNetworkInterface.getPort());   // + disNetworkInterface.getDisPort());
+        pduListener = new DisThreadedNetworkInterface.PduListener()
+        {
+            /** Callback handler for listener */
+            @Override
+            public void incomingPdu(Pdu newPdu)
+            {
+                receivedPdu = newPdu;
+            }
+        };
+        disNetworkInterface.addListener(pduListener);
+        
+        String outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
+        System.out.println("Beginning pdu save to directory " + outputDirectory);
+        pduRecorder = new PduRecorder(outputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save
+        pduRecorder.setEncodingPduLog(PduRecorder.ENCODING_PLAINTEXT);
+        pduRecorder.setVerbose(true); // either sending, receiving or both
+        pduRecorder.start(); // begin running
+    }
+
+    /** All done, release network resources */
+    public void tearDownNetworkInterface()
+    {
+        pduRecorder.stop(); // handles disNetworkInterface.close(), tears down threads and sockets
+    }
+
+    /** 
+     * Send a single Protocol Data Unit (PDU) of any type
+     * @param pdu the pdu to send
+     */
+    protected void sendSinglePdu(Pdu pdu)
+    {
+        try
+        {
+            disNetworkInterface.sendPDU(pdu);
+            Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
+        } 
+        catch (InterruptedException ex)
+        {
+            System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Send Comment PDU
+     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
+     * @param commentType    enumeration value describing purpose of the narrative comment
+     * @param comments       String array of narrative comments
+     */
+    public void sendCommentPdu(VariableRecordType commentType,
+                                     // vararg... variable-length set of String comments can optionally follow
+                                        String... comments)
+    {
+        sendAllPdusForLoopTimestep (null, null, commentType, comments);
+    }
+
+    /**
+     * Send EntityState, Fire, Comment PDUs that got updated for this loop, reflecting state of current simulation timestep.
+     * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments
+     * @param entityStatePdu the ESPDU to send, if any
+     * @param firePdu        the FirePDU to send, if any
+     * @param commentType    enumeration value describing purpose of the narrative comment
+     * @param comments       String array of narrative comments
+     */
+    public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu,
+                                   FirePdu firePdu,
+                        VariableRecordType commentType,
+                              // vararg... variable-length set of String comments can optionally follow
+                                 String... comments)
+    {
+        if (entityStatePdu != null)
+            sendSinglePdu(entityStatePdu);
+            
+        if (firePdu != null)
+            sendSinglePdu(firePdu); // bang
+        
+        if ((comments != null) && (comments.length > 0))
+        {
+            ArrayList<String> newCommentsList = new ArrayList<>();
+            for (String comment : comments)
+            {
+                if (!comment.isEmpty())
+                {
+                    newCommentsList.add(comment); // OK found something to send
+                }
+            }
+            if (!newCommentsList.isEmpty())
+            {
+                if (commentType == null)
+                    commentType = otherComment; // fallback value otherComment
+                // now build the commentPdu from these string inputs, thus constructing a narrative entry
+                CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments);
+                sendSinglePdu(commentPdu);
+                if (isVerboseComments())
+                {
+                    System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString());
+                    System.out.flush();
+                }
+            }
+        }
+    }
+
+    /**
+     * test for verboseComments mode
+     * @return whether verboseComments mode is enabled
+     */
+    public boolean isVerboseComments() {
+        return verboseComments;
+    }
+
+    /**
+     * set verboseComments mode
+     * @param newVerboseComments whether verboseComments mode is enabled
+     */
+    public void setVerboseComments(boolean newVerboseComments) {
+        this.verboseComments = newVerboseComments;
+    }
+    
+    /**
+     * Initial execution via main() method: handle args array of command-line initialization (CLI) arguments here
+     * @param args command-line parameters: network address and port
+     */
+    protected void handleArgs (String[] args)
+    {
+        // initial execution: handle args array of initialization arguments here
+        if (args.length == 2) 
+        {
+            if ((args[0] != null) && !args[0].isEmpty())
+                thisProgram.setNetworkAddress(args[0]);
+            if ((args[1] != null) && !args[1].isEmpty())
+                thisProgram.setNetworkPort(Integer.parseInt(args[1]));
+        }
+        else if (args.length != 0) 
+        {
+            System.err.println("Usage: " + thisProgram.getClass().getSimpleName() + " [address port]");
+            System.exit(-1);
+        }
+    }
+    
+    /** Locally instantiable copy of program, can be subclassed. */
+    protected static ExampleSimulationProgramTam thisProgram;
+  
+    /**
+     * Main method is first executed when a program instance is loaded.
+     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
+     * @param args command-line parameters: network address and port.
+     *    Command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
+     */
+    public static void main(String[] args)
+    {
+        TRACE_PREFIX = "[" + ExampleSimulationProgramTam.class.getName() + "] ";
+        
+        System.out.println(TRACE_PREFIX + "main() started...");
+        
+        thisProgram = new ExampleSimulationProgramTam(); // creates instance of self within static main() method
+        
+        thisProgram.handleArgs (args); // process command-line invocation arguments
+
+        thisProgram.setUpNetworkInterface();
+        
+//        thisProgram.pduRecorder.setDescriptor (TRACE_PREFIX.replace("[","").replace("]","") + " pduRecorder");
+
+        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
+        
+        thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering
+        
+        System.out.println(TRACE_PREFIX + "complete."); // report successful completion
+        
+        System.exit(0); // ensure all threads and sockets released
+    }
+}
diff --git a/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/PduTrack.java b/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/PduTrack.java
index 504ea3db17ddc26610c304f9c2ee1cfeefa57cd1..8d54bbc547ef170a9438275b9d45d39a56ad4f1f 100644
--- a/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/PduTrack.java
+++ b/assignments/src/MV3500Cohort2022MayJune/homework2/Tam/PduTrack.java
@@ -1,1043 +1,1043 @@
-/*
-Copyright (c) 1995-2022 held by the author(s).  All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright
-      notice, this list of conditions and the following disclaimer
-      in the documentation and/or other materials provided with the
-      distribution.
-    * Neither the names of the Naval Postgraduate School (NPS)
-      Modeling Virtual Environments and Simulation (MOVES) Institute
-      https://www.nps.edu and https://www.nps.edu/web/moves
-      nor the names of its contributors may be used to endorse or
-      promote products derived from this software without specific
-      prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-*/
-// TODO move into opendis7 distribution tree in package edu.nps.moves.dis7.utilities.stream;
-
-package MV3500Cohort2022MayJune.homework2.Tam;
-
-import MV3500Cohort2022MayJune.homework2.Duran.*;
-import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
-import edu.nps.moves.dis7.enumerations.DisPduType;
-import edu.nps.moves.dis7.enumerations.ForceID;
-import edu.nps.moves.dis7.pdus.EntityID;
-import edu.nps.moves.dis7.pdus.EntityStatePdu;
-import edu.nps.moves.dis7.pdus.EulerAngles;
-import edu.nps.moves.dis7.pdus.Pdu;
-import edu.nps.moves.dis7.pdus.Vector3Double;
-import edu.nps.moves.dis7.utilities.DisTime;
-import edu.nps.moves.dis7.utilities.PduFactory;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
-/**
- * Create a track from DIS ESPDUs
- * @author brutzman
- */
-public class PduTrack
-{   
-    private String          descriptor = new String();
-    private ArrayList<Pdu>  pduList = new ArrayList<>();
-    private EntityStatePdu  initialEspdu;
-    private EntityStatePdu  latestEspdu;
-    private Vector3Double   initialLocation;
-    private Vector3Double   latestLocation;
-    
-    /** waypoint timelineList in seconds */
-    private ArrayList<Float>          timelineList = new ArrayList<>();
-    private ArrayList<Vector3Double> waypointsList = new ArrayList<>();
-    private ArrayList<EulerAngles> eulerAnglesList = new ArrayList<>();
-    
-    private String                             author = new String();
-    private String                 x3dModelIdentifier = new String();
-    private String                 x3dModelName       = "PduTrackInterpolation.x3d";
-    private float             defaultWaypointInterval = -1;
-    private float                     durationSeconds = -1;
-    private String                   x3dTimeSensorDEF = new String();
-    private String         x3dPositionInterpolatorDEF = new String();
-    private String      x3dOrientationInterpolatorDEF = new String();
-    private boolean      addLineBreaksWithinKeyValues = false;
-    /** what kind of timestamp is being used */
-    public    DisTime.TimestampStyle timestampStyle        = DisTime.TimestampStyle.IEEE_ABSOLUTE;
-    /** direct access to pduFactory for creating new PDU instances */
-    protected PduFactory             pduFactory            = new PduFactory(timestampStyle);
-    private   LocalDateTime          recordingStart;
-    private   LocalDateTime          recordingStop;
-    private   String                 todaysDateString      = new String();
-    
-    /** direct access to byteArrayOutputStream */
-    protected ByteArrayOutputStream  byteArrayOutputStream = new ByteArrayOutputStream();
-    /** direct access to DataOutputStream */
-    protected DataOutputStream       dataOutputStream      = new DataOutputStream(byteArrayOutputStream);
-    private   String TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName()) + "] ";
-    
-    /**
-     * Constructor for PduTrack
-     */
-    public PduTrack()
-    {
-        // initialization code here
-        
-        // https://docs.oracle.com/javase/tutorial/datetime/TOC.html
-        // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/package-summary.html
-        // https://stackoverflow.com/questions/5175728/how-to-get-the-current-date-time-in-java/5175900 (scroll down to java.time)
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy");
-        todaysDateString = LocalDate.now().format(formatter);
-//      System.out.println(TRACE_PREFIX + "today=" + todayString);
-    }
-    
-    /**
-     * Set timestampStyle used by PduFactory
-     * @param newTimestampStyle new value to set
-     */
-    public PduTrack(DisTime.TimestampStyle newTimestampStyle)
-    {
-        timestampStyle = newTimestampStyle;
-        DisTime.setTimestampStyle(newTimestampStyle);
-        // automatic super invocation: return PduTrack();
-    }
-    
-    /**
-     * Get simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
-     * @return simple descriptor name
-     */
-    public String getDescriptor() 
-    {
-        return descriptor;
-    }
-    /**
-     * Set new simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
-     * @param newDescriptor simple descriptor name for this interface
-     * @return same object to permit progressive setters */
-    public PduTrack setDescriptor(String newDescriptor) 
-    {
-        if (newDescriptor != null)
-            this.descriptor = newDescriptor.trim();
-        TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName() + " " + descriptor) + "] ";
-        return this;
-    }
-    /**
-     * Get timestampStyle used by PduFactory
-     * @return current timestampStyle
-     */
-    public DisTime.TimestampStyle getTimestampStyle()
-    {
-        return timestampStyle;
-    }
-    /**
-     * Set timestampStyle used by PduFactory
-     * @param newTimestampStyle the timestampStyle to set
-     * @return same object to permit progressive setters 
-     */
-    public PduTrack setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
-    {
-        this.timestampStyle = newTimestampStyle;
-        DisTime.setTimestampStyle(newTimestampStyle);
-        return this;
-    }
-
-    /**
-     * Determine initial location, reset to (0 0 0) if not found
-     * @return current initialLocation
-     */
-    public Vector3Double getInitialLocation() {
-        if (initialLocation == null)
-        {
-            System.out.println (TRACE_PREFIX + "getInitialLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
-            return new Vector3Double();
-        }
-        return initialLocation;
-    }
-    /**
-     * Determine current location, reset to (0 0 0) if not found
-     * @return current latestLocation
-     */
-    public Vector3Double getLatestLocation() {
-        if (latestLocation == null)
-        {
-            System.out.println (TRACE_PREFIX + "getLatestLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
-            return new Vector3Double();
-        }
-        return latestLocation;
-    }
-    /**
-     * Get individual Pdu from pduList, index must not exceed existing pduList size
-     * @param index for pdu of interest
-     * @return pdu of interest
-     */
-    public Pdu getPdu(int index) throws IndexOutOfBoundsException
-    {
-        if ((index >= pduList.size()) || (index < 0))
-        {
-            System.out.println (TRACE_PREFIX + "getPdu(" + index + ") out of bounds, pduList.size()=" + pduList.size());
-            // then throw exception
-        }
-        return pduList.get(index);
-    }
-    /**
-     * get current pduList
-     * @return current pduList
-     */
-    public ArrayList<Pdu> getPduList() {
-        return pduList;
-    }
-    /**
-     * get current waypointsList
-     * @return current waypointsList
-     */
-    public ArrayList<Vector3Double> getWaypointsList() {
-        return waypointsList;
-    }
-    /**
-     * current eulerAnglesList
-     * @return current eulerAnglesList
-     */
-    public ArrayList<EulerAngles> getEulerAnglesList() {
-        return eulerAnglesList;
-    }
-    /**
-     * Time in seconds corresponding to each PDU
-     * @return current timelineList
-     */
-    public ArrayList<Float> getTimelineList() {
-        return timelineList;
-    }
-    /**
-     * Add PDU, typically ESPDU
-     * @param newPdu new Pdu to add, typically ESPDU
-     * @return same object to permit progressive setters
-     */
-    public PduTrack addPdu(Pdu newPdu)
-    {
-        if (newPdu.getPduType() == DisPduType.ENTITY_STATE)
-        {
-////          EntityStatePdu deepCopyEspdu = new EntityStatePdu();
-//            EntityStatePdu deepCopyEspdu = pduFactory.makeEntityStatePdu();
-//            deepCopyEspdu.setTimestamp        (((EntityStatePdu)newPdu).getTimestamp());
-//            deepCopyEspdu.setMarking          (((EntityStatePdu)newPdu).getMarking());
-//            deepCopyEspdu.setEntityID         (((EntityStatePdu)newPdu).getEntityID());
-//            deepCopyEspdu.setForceId          (((EntityStatePdu)newPdu).getForceId());
-//            deepCopyEspdu.setEntityType       (((EntityStatePdu)newPdu).getEntityType());
-//            deepCopyEspdu.setEntityLocation   (((EntityStatePdu)newPdu).getEntityLocation());
-//            deepCopyEspdu.setEntityOrientation(((EntityStatePdu)newPdu).getEntityOrientation());
-            
-            EntityStatePdu deepCopyEspdu = ((EntityStatePdu)newPdu).copy();
-            pduList.add(deepCopyEspdu);
-//          alternative trials:
-//          pduList.add(((EntityStatePdu)newPdu).copyDataOutputStream());
-//          pduList.add(((EntityStatePdu)newPdu).copy());
-            
-            if (initialLocation == null)
-            {
-                initialEspdu    = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
-                initialLocation = deepCopyEspdu.getEntityLocation();
-            }
-            latestEspdu         = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
-            latestLocation      = deepCopyEspdu.getEntityLocation();
-        }
-        else pduList.add(newPdu); // TODO copy() - careful, must be a new object and not a reference
-        return this;
-    }
-    /**
-     * clear all PDUs
-     * @return same object to permit progressive setters
-     */
-    public PduTrack clearPduLists()
-    {
-           getPduList().clear();
-          waypointsList.clear();
-        eulerAnglesList.clear();
-           timelineList.clear();
-           initialEspdu = null;
-            latestEspdu = null;
-        initialLocation = null;
-         latestLocation = null;
-        return this;
-    }
-    
-    private String normalizeNameToken(String candidateDEF)
-    {
-        return candidateDEF.replace(" ", "").replace("-", "")
-                           .replace("(", "").replace(")", "")
-                           .replace("[", "").replace("])", "");
-    }
-
-    /**
-     * Provide DEF value if not defined by program
-     * @return current x3dTimeSensorDEF
-     */
-    public String getX3dTimeSensorDEF() {
-        if    (x3dTimeSensorDEF.isEmpty())
-               x3dTimeSensorDEF = normalizeNameToken(getDescriptor()) + "Clock";
-        return x3dTimeSensorDEF;
-    }
-
-    /**
-     * Set DEF value for X3D node
-     * @param x3dTimeSensorDEF the x3dTimeSensorDEF to set
-     */
-    public void setX3dTimeSensorDEF(String x3dTimeSensorDEF) {
-        this.x3dTimeSensorDEF = normalizeNameToken(x3dTimeSensorDEF);
-    }
-
-    /**
-     * Provide DEF value if not defined by program
-     * @return current x3dPositionInterpolatorDEF
-     */
-    public String getX3dPositionInterpolatorDEF() {
-        if    (x3dPositionInterpolatorDEF.isEmpty())
-               x3dPositionInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Positions";
-        return x3dPositionInterpolatorDEF;
-    }
-
-    /**
-     * Set DEF value for X3D node
-     * @param x3dPositionInterpolatorDEF the x3dPositionInterpolatorDEF to set
-     */
-    public void setX3dPositionInterpolatorDEF(String x3dPositionInterpolatorDEF) {
-        this.x3dPositionInterpolatorDEF = normalizeNameToken(x3dPositionInterpolatorDEF);
-    }
-
-    /**
-     * Provide DEF value if not defined by program
-     * @return current x3dOrientationInterpolatorDEF
-     */
-    public String getX3dOrientationInterpolatorDEF() {
-        if    (x3dOrientationInterpolatorDEF.isEmpty())
-               x3dOrientationInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Orientations";
-        return x3dOrientationInterpolatorDEF;
-    }
-
-    /**
-     * Set DEF value for X3D node
-     * @param x3dOrientationInterpolatorDEF the x3dOrientationInterpolatorDEF to set
-     */
-    public void setX3dOrientationInterpolatorDEF(String x3dOrientationInterpolatorDEF) {
-        this.x3dOrientationInterpolatorDEF = normalizeNameToken(x3dOrientationInterpolatorDEF);
-    }
-
-    /**
-     * Sort all PDUs by timestamp
-     * @see <a href="https://stackoverflow.com/questions/16252269/how-to-sort-an-arraylist">StackOverflow: How to sort an ArrayList?</a>
-     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
-     * @return same object to permit progressive setters
-     */
-    public PduTrack sortPdus()
-    {
-        Collections.sort(pduList, new Comparator<Pdu>() {
-            @Override
-            public int compare(Pdu lhs, Pdu rhs)
-            {
-                // -1 less than, 1 greater than, 0 equal
-                if      (lhs.occursBefore(rhs))
-                         return -1;
-                else if (lhs.occursSameTime(rhs))
-                         return 0;
-                else     return 1;
-            }
-        });
-        return this;
-    }
-    /**
-     * Reverse order of PDU list
-     * @see <a href="https://stackoverflow.com/questions/10766492/what-is-the-simplest-way-to-reverse-an-arraylist">StackOverflow: What is the Simplest Way to Reverse an ArrayList?</a>
-     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
-     * @return same object to permit progressive setters
-     */
-    public PduTrack reversePdus()
-    {
-        Collections.reverse(pduList);
-        return this;
-    }
-
-    /**
-     * Determine whether any ESPDUs have been received by this track
-     * @return whether track is empty
-     */
-    public boolean isTrackEmpty()
-    {
-        return (getEspduCount() == 0);
-    }
-    /**
-     * Count ESPDUs in pduList
-     * @return number of ESPDUs in pduList
-     */
-    public int getEspduCount()
-    {
-        int counter = 0;
-        for (Pdu nextPdu : getPduList())
-        {
-            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
-               counter += 1;
-        }
-        return counter;
-    }
-    /**
-     * Compute track duration in timestamp ticks
-     * @return duration in timestamp ticks between initial and final ESPDU timestamps in waypointList
-     */
-    public int getTotalDurationTicks()
-    {
-        int    initialTime = -1;
-        int      finalTime = -1;
-        int  durationTicks = -1; // used if pduList is empty
-        
-        // must skip through pduList since non-ESPDU PDUs may be present
-        for (Pdu nextPdu : getPduList())
-        {
-            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
-            {
-                if (initialTime == -1)
-                    initialTime = nextPdu.getTimestamp();
-                finalTime = nextPdu.getTimestamp();
-            }
-        }
-        if ((initialTime >= 0) && (finalTime >= 0))
-             durationTicks = (finalTime - initialTime);
-        if (getPduList().isEmpty())
-        {
-            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to empty pdu list");
-        }
-        else if ((durationTicks <= 0) && (defaultWaypointInterval <= 0))
-        {
-            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to illegal pdu list");
-        }
-        return durationTicks;
-    }
-    /**
-     * Compute track duration in seconds
-     * @return duration in seconds between initial and final ESPDU timestamps in waypointList
-     */
-    public float getTotalDurationSeconds()
-    {
-        if (defaultWaypointInterval > 0)
-        {
-            return getEspduCount() * defaultWaypointInterval;
-        }
-        else if (getTotalDurationTicks() < 0)
-               durationSeconds = getTotalDurationTicks() * 1.0f; // TODO convert
-        return durationSeconds;
-    }
-    /**
-     * Create waypoints and angles using all ESPDU points, with no linear regression or array reduction.
-     * @return same object to permit progressive setters
-     */
-    public PduTrack createRawWaypoints()
-    {
-        // https://stackoverflow.com/questions/6536094/java-arraylist-copy
-        
-           timelineList.clear();
-          waypointsList.clear();
-        eulerAnglesList.clear();
-        float clock = 0.0f;
-        for (int i = 0; i < pduList.size(); i++)
-        {
-            Pdu nextPdu = pduList.get(i);
-            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
-            {
-                EntityStatePdu espdu = (EntityStatePdu)nextPdu;
-                if (defaultWaypointInterval > 0)
-                {
-                    timelineList.add(clock);
-                    clock += defaultWaypointInterval;
-                }
-                else
-                {
-                    timelineList.add(espdu.getTimestamp() * 1.0f); // TODO convert
-                }
-                   waypointsList.add(espdu.getEntityLocation());
-                 eulerAnglesList.add(espdu.getEntityOrientation());
-            }
-        }
-        return this;
-    }
-    /** 
-     * Utility method to create TimeSensor
-     * @return TimeSensor string in XML format
-     */
-    public String createX3dTimeSensorString()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("    <TimeSensor");
-        sb.append(" DEF='").append(getX3dTimeSensorDEF()).append("'");
-        sb.append(" cycleInterval='").append(String.valueOf(getTotalDurationSeconds())).append("'");
-        sb.append(" loop='true'");
-        sb.append("/>").append("\n");
-        
-        return sb.toString();
-    }
-    /**
-     * Create PositionInterpolator from Pdu list
-     * @return X3D PositionInterpolator as string
-     */
-    public String createX3dPositionInterpolatorString()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("    <PositionInterpolator");
-        sb.append(" DEF='").append(getX3dPositionInterpolatorDEF()).append("'");
-        sb.append(" key='");
-        for (int i = 0; i < timelineList.size(); i++)
-        {
-            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
-            sb.append(String.valueOf(nextDuration));
-            if (i < timelineList.size() - 1)
-                sb.append(" ");
-        }
-        sb.append("'");
-        sb.append(" keyValue='");
-        for (int i = 0; i < waypointsList.size(); i++)
-        {
-            if (hasAddLineBreaksWithinKeyValues())
-                sb.append("\n");
-            Vector3Double nextPosition = waypointsList.get(i);
-            sb.append(String.valueOf(nextPosition.getX())).append(" ")
-              .append(String.valueOf(nextPosition.getY())).append(" ")
-              .append(String.valueOf(nextPosition.getZ()));
-            if (i < waypointsList.size() - 1)
-                sb.append(",");
-        }
-        sb.append("'");
-        sb.append("/>").append("\n");
-        
-        return sb.toString();
-    }
-    /**
-     * Create OrientationInterpolator from Pdu list
-     * TODO preliminary support only includes horizontal rotation.
-     * @return X3D OrientationInterpolator as string
-     */
-    public String createX3dOrientationInterpolatorString()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("    <OrientationInterpolator");
-        sb.append(" DEF='").append(getX3dOrientationInterpolatorDEF()).append("'");
-        sb.append(" key='");
-        for (int i = 0; i < timelineList.size(); i++)
-        {
-            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
-            sb.append(String.valueOf(nextDuration));
-            if (i < timelineList.size() - 1)
-                sb.append(" ");
-        }
-        sb.append("'");
-        sb.append(" keyValue='");
-        for (int i = 0; i < eulerAnglesList.size(); i++)
-        {
-            if (hasAddLineBreaksWithinKeyValues())
-                sb.append("\n");
-            EulerAngles nextEulerAngle = new EulerAngles();
-            float axisX = 0.0f;
-            float axisY = 1.0f;
-            float axisZ = 0.0f;
-            float angle = 0.0f; // radians
-            
-            nextEulerAngle = eulerAnglesList.get(i);
-            angle = nextEulerAngle.getTheta();
-            
-            sb.append(String.valueOf(axisX)).append(" ")
-              .append(String.valueOf(axisY)).append(" ")
-              .append(String.valueOf(axisZ)).append(" ")
-              .append(String.valueOf(angle));
-            if (i < eulerAnglesList.size() - 1)
-                sb.append(",");
-        }
-        sb.append("'");
-        sb.append("/>").append("\n");
-        
-        return sb.toString();
-    }
-
-    /**
-     * Get name of author used as creator of X3D model
-     * @return current author
-     */
-    public String getAuthor() {
-        return author;
-    }
-
-    /**
-     * Set name of author used as creator of X3D model
-     * @param author the author to set
-     */
-    public void setAuthor(String author) {
-        if  (author == null)
-             author = new String();
-        author = author.trim();
-        this.author = author;
-    }
-
-    /**
-     * Get name of online url identifier for X3D model
-     * @return current x3dModelIdentifier
-     */
-    public String getX3dModelIdentifier() {
-        return x3dModelIdentifier;
-    }
-
-    /**
-     * Set name of online url identifier for X3D model
-     * @param x3dModelIdentifier the x3dModelIdentifier to set
-     */
-    public void setX3dModelIdentifier(String x3dModelIdentifier) {
-        if  (x3dModelIdentifier == null)
-             x3dModelIdentifier = new String();
-        x3dModelIdentifier = x3dModelIdentifier.trim();
-        if (!x3dModelIdentifier.startsWith("http://") && !x3dModelIdentifier.startsWith("https://"))
-            System.out.println(TRACE_PREFIX + "warning, identifier typically begins with https:// or http://");
-        else this.x3dModelIdentifier = x3dModelIdentifier;
-    }
-
-    /**
-     * File name for X3D model production
-     * @return current x3dModelName
-     */
-    public String getX3dModelName() {
-        return x3dModelName;
-    }
-    /**
-     * File name for X3D model production
-     * @param x3dModelName the x3dModelName to set
-     */
-    public void setX3dModelName(String x3dModelName) {
-        if  (x3dModelName == null)
-             x3dModelName = new String();
-        x3dModelName = x3dModelName.trim();
-        this.x3dModelName = x3dModelName;
-    }
-
-    /**
-     * Verbose (but more readable) output of numeric arrays in X3D model
-     * @return current addLineBreaksWithinKeyValues
-     */
-    public boolean hasAddLineBreaksWithinKeyValues() {
-        return addLineBreaksWithinKeyValues;
-    }
-
-    /**
-     * Verbose (but more readable) output of numeric arrays in X3D model
-     * @param addLineBreaksWithinKeyValues the addLineBreaksWithinKeyValues to set
-     */
-    public void setAddLineBreaksWithinKeyValues(boolean addLineBreaksWithinKeyValues) {
-        this.addLineBreaksWithinKeyValues = addLineBreaksWithinKeyValues;
-    }
-    /**
-     * Create full X3D interpolator model from Pdu list, assembling sections of scene graph
-     * @return X3D model as string
-     */
-    public String createX3dModel()
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append(createX3dModelHeaderString());
-        sb.append(createX3dTimeSensorString());
-        sb.append(createX3dPositionInterpolatorString());
-        sb.append(createX3dOrientationInterpolatorString());
-        sb.append(createX3dRoutesGeometryFooterString());
-        return sb.toString();
-    }
-    /**
-     * Create PositionInterpolator from Pdu list
-     * @return X3D PositionInterpolator as string
-     */
-    public String createX3dModelHeaderString()
-    {        
-        StringBuilder sb = new StringBuilder();
-        
-        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("\n");
-        sb.append("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 4.0//EN\" \"https://www.web3d.org/specifications/x3d-4.0.dtd\">").append("\n");
-        sb.append("<X3D profile='Interchange' version='4.0' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-4.0.xsd'>").append("\n");
-        sb.append("  <head>").append("\n");
-        if (!getX3dModelName().isEmpty())
-            sb.append("    <meta content='").append(getX3dModelName()).append("' name='title'/>").append("\n");
-        sb.append("    <meta content='Conversion of ESPDU track into X3D animation interpolators and LineSet.' name='description'/>").append("\n");
-        
-        sb.append("    <meta content='1 January 2022' name='created'/>").append("\n");
-        sb.append("    <meta content='").append(todaysDateString).append("' name='modified'/>").append("\n");
-        if (!getAuthor().isEmpty())
-            sb.append("    <meta content='").append(getAuthor()).append("' name='creator'/>").append("\n");
-        if (!getX3dModelIdentifier().isEmpty())
-            sb.append("    <meta content='").append(getX3dModelIdentifier()).append("' name='identifier'/>").append("\n");
-        
-        sb.append("    <meta content='PduTrack utility, opendis7-java Library https://github.com/open-dis/opendis7-java' name='generator'/>").append("\n");
-        sb.append("    <meta content='NPS MOVES MV3500 Networked Graphics https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Resources https://www.web3d.org/x3d/content/examples/X3dResources.html' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Scene Authoring Hints https://www.web3d.org/x3d/content/examples/X3dSceneAuthoringHints.html' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Tooltips https://www.web3d.org/x3d/tooltips/X3dTooltips.html' name='reference'/>").append("\n");
-        sb.append("    <meta content='X3D Validator https://savage.nps.edu/X3dValidator' name='reference'/>").append("\n");
-        sb.append("    <meta content='Open source https://raw.githubusercontent.com/open-dis/opendis7-java/master/license.html' name='license'/>").append("\n");
-        sb.append("  </head>").append("\n");
-        sb.append("  <Scene>").append("\n");
-        sb.append("    <WorldInfo title='PduTrackInterpolation.x3d'/>").append("\n");
-
-        return sb.toString(); 
-    }
-    /**
-     * Create X3D ROUTEs and footer to connect TimeSensor to interpolators
-     * @return X3D PositionInterpolator as string
-     */
-    public String createX3dRoutesGeometryFooterString()
-    {
-        StringBuilder sb = new StringBuilder();
-        
-        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
-          .append(getX3dTimeSensorDEF())
-          .append("' toField='set_fraction' toNode='")
-          .append(getX3dPositionInterpolatorDEF())
-          .append("'/>").append("\n");
-        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
-          .append(getX3dTimeSensorDEF())
-          .append("' toField='set_fraction' toNode='")
-          .append(getX3dOrientationInterpolatorDEF())
-          .append("'/>").append("\n");
-        
-        sb.append("    <Shape>").append("\n");
-        sb.append("      <Appearance DEF='TrackAppearance'>").append("\n");
-        sb.append("        <Material emissiveColor='0.2 0.8 0.8'/>").append("\n");
-        sb.append("      </Appearance>").append("\n");
-        sb.append("      <LineSet vertexCount='").append(waypointsList.size()).append("'>").append("\n");
-        sb.append("        <Coordinate point='");
-        for (int i = 0; i < waypointsList.size(); i++)
-        {
-            if (hasAddLineBreaksWithinKeyValues())
-                sb.append("\n");
-            Vector3Double nextPosition = waypointsList.get(i);
-            sb.append(String.valueOf(nextPosition.getX())).append(" ")
-              .append(String.valueOf(nextPosition.getY())).append(" ")
-              .append(String.valueOf(nextPosition.getZ()));
-            if (i < waypointsList.size() - 1)
-                sb.append(",");
-        }
-        sb.append("'/>").append("\n");
-        sb.append("      </LineSet>").append("\n");
-        sb.append("    </Shape>").append("\n");
-        sb.append("    <Transform DEF='AnimationTransform'>").append("\n");
-        sb.append("      <Transform rotation='0 0 1 1.57'>").append("\n");
-        sb.append("        <Shape>").append("\n");
-        sb.append("          <Appearance USE='TrackAppearance'/>").append("\n");
-        sb.append("          <Cone bottomRadius='0.5'/>").append("\n");
-        sb.append("        </Shape>").append("\n");
-        sb.append("      </Transform>").append("\n");
-        sb.append("    </Transform>").append("\n");
-        sb.append("    <ROUTE fromField='value_changed' fromNode='")
-          .append(getX3dPositionInterpolatorDEF())
-          .append("' toField='translation' toNode='AnimationTransform'/>").append("\n");
-        sb.append("    <ROUTE fromField='value_changed' fromNode='")
-          .append(getX3dOrientationInterpolatorDEF())
-          .append("' toField='rotation' toNode='AnimationTransform'/>").append("\n");
-        
-        sb.append("  </Scene>").append("\n");
-        sb.append("</X3D>").append("\n");
-        
-        return sb.toString();
-    }
-
-    /**
-     * get defaultWaypointInterval
-     * @return current wayPointInterval
-     */
-    public float getDefaultWaypointInterval() {
-        return defaultWaypointInterval;
-    }
-
-    /**
-     * Set uniform waypoint interval (currently for testing)
-     * @param newWaypointInterval the wayPointInterval to set, in seconds, must be greater than zero
-     * @return same object to permit progressive setters */
-    public PduTrack setDefaultWaypointInterval(float newWaypointInterval) {
-        if (newWaypointInterval > 0.0)
-            this.defaultWaypointInterval = newWaypointInterval;
-        else 
-        {
-            System.out.println(TRACE_PREFIX + "error in setWaypointInterval(newWaypointInterval=" + newWaypointInterval + ") must be greater than zero");
-            return this;
-        }
-            
-        float clock = 0.0f;
-        if (!timelineList.isEmpty())
-        {   
-            ArrayList<Float> newTimelineList = new ArrayList<>();
-            for (int i = 0; i < getEspduCount(); i++)
-            {
-                newTimelineList.add(clock);
-                clock += defaultWaypointInterval;
-            }
-            timelineList = newTimelineList; // TO Array copy?
-        }
-        return this;
-    }
-    /** whether or not to insert commas between hex values */
-    private boolean insertCommas = true;
-    /**
-     * determine whether comma insertion is turned on
-     * @return whether or not to insert commas between hex values
-     */
-    public boolean hasInsertCommas() {
-        return insertCommas;
-    }
-    /**
-     * set whether comma insertion is turned on
-     * @param insertCommas the insertCommas value to set
-     */
-    public void setInsertCommas(boolean insertCommas) {
-        this.insertCommas = insertCommas;
-    }
-    /**
-     * Convert byte array to hex string
-     * @param bytes input data
-     * @param insertCommas whether to insert commas between hex values
-     * @return hex string
-     */
-    public String bytesToHex(byte[] bytes, boolean insertCommas)
-    {
-        this.setInsertCommas(insertCommas);
-        return bytesToHex(bytes);
-    }
-    /**
-     * Convert byte array to hex string
-     * @param bytes input data
-     * @see <a href="https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java">https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java</a>
-     * @return hex string
-     */
-    public static String bytesToHex(byte[] bytes)
-    {
-        boolean insertCommas = true;
-        final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-        char[] hexChars = new char[bytes.length * 2];
-        StringBuilder sb = new StringBuilder();
-        for (int j = 0; j < bytes.length; j++) {
-            int v = bytes[j] & 0xFF;
-            hexChars[j * 2]     = HEX_ARRAY[v >>> 4];
-            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
-//          if (!(hexChars[j * 2] == '0')) // omit leading zero
-            sb.append(hexChars[j * 2]);
-            sb.append(hexChars[j * 2 + 1]);
-            if (insertCommas && (j < bytes.length - 1))
-                sb.append(", ");
-        }
-        return sb.toString();
-    }
-    /**
-     * Report current PDU information to console
-     * @param anEspdu EntityStatePdu of interest
-     * @return same object to permit progressive setters
-     */
-    public PduTrack reportPdu(EntityStatePdu anEspdu)
-    {
-        System.out.println (
-                String.format("%s", anEspdu.getMarkingString().trim()) + ", " +
-                DisTime.convertToString(anEspdu.getTimestamp()) + " (" +
-                String.format("%08d", anEspdu.getTimestamp()) + "), " + 
-                "EntityID=(" + 
-                anEspdu.getEntityID().getSiteID() + ", " +
-                anEspdu.getEntityID().getApplicationID() + ", " + 
-                anEspdu.getEntityID().getEntityID() + "), " +
-                "location=(" + 
-                String.format("%4.1f", anEspdu.getEntityLocation().getX()) + ", " +
-                String.format("%4.1f", anEspdu.getEntityLocation().getY()) + ", " + 
-                String.format("%4.1f", anEspdu.getEntityLocation().getZ()) + ")"
-//              + " " + espdu_1.getEntityLinearVelocity().toString()
-        );
-        return this;
-    }
-    
-    /** Flush all buffers to reduce console scrambling while threaded
-     */
-    protected void flushBuffers()
-    {
-        try
-        {
-                 dataOutputStream.flush(); 
-            byteArrayOutputStream.flush();
-            byteArrayOutputStream.reset();
-            System.err.flush(); 
-            System.out.flush();
-        }
-        catch (IOException ioe)
-        {
-            System.out.println(TRACE_PREFIX + "flushBuffers() IOException: " + ioe.getMessage());
-        }
-    }
-    
-    /** Self test to check basic operation, invoked by main()
-     */
-    @SuppressWarnings("SleepWhileInLoop")
-    public void selfTest()
-    {
-        final int TOTAL_PDUS = 5;
-        System.out.println(TRACE_PREFIX + "selfTest() start...");
-      
-        PduTrack pduTrack = new PduTrack();
-        pduTrack.setDescriptor("PduTrack Self Test");
-        pduTrack.setAuthor("Don Brutzman");
-        pduTrack.setX3dModelIdentifier("https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/PduTrackInterpolation.x3d");
-        pduTrack.setDefaultWaypointInterval(1.0f); // experimentation with timestamp values
-
-        DisTime.setEpochLvcNow();
-        recordingStart = LocalDateTime.now();
-        Instant epoch = DisTime.getEpochLvc();
-        System.out.println(TRACE_PREFIX + "DisTime.hasEpochLvc()=" + DisTime.hasEpochLvc() + 
-                                         ", DisTime.getEpochLvc()=" + epoch + 
-                                         ", Instant.now()=" + Instant.now());
-        
-        EntityID entityID_123 = new EntityID();
-        entityID_123.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
-        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
-
-        for (int i = 0; i < TOTAL_PDUS; i++) // create espdus and add each to track pduList
-        {
-//          EntityStatePdu espdu = new EntityStatePdu();
-            EntityStatePdu espdu = pduFactory.makeEntityStatePdu(); // TODO check Pdu.Type
-            espdu.setTimestamp(DisTime.getCurrentDisTimestamp()); // chooses appropriate version
-            espdu.setMarking("ESPDU " + i);
-            espdu.setEntityLocation(i, i, i);
-            espdu.setEntityOrientation(0, (float)(45.0 * Math.PI / 180.0), 0);
-            espdu.setEntityID(entityID_123);
-            espdu.setForceId(ForceID.FRIENDLY);
-            espdu.setEntityType(new _001Poseidon()); // note import statement above
-            pduTrack.addPdu(espdu); // create copy
-            reportPdu(espdu);
-            try
-            {
-                Thread.sleep(100l);
-            }
-            catch (InterruptedException ie)
-            {
-                System.out.println(TRACE_PREFIX + "exceptional sleep dulay when generating ESPDUs: " + ie.getMessage());
-            }
-        }
-//        System.out.println(TRACE_PREFIX + "reversePdus() then sortPdus() to test track operations");
-//        pduTrack.reversePdus(); // test
-//        pduTrack.sortPdus();    // restore
-        pduTrack.createRawWaypoints(); // copies all ESPDU points to waypoints
-        
-        System.out.println(TRACE_PREFIX + "getEspduCount()="              + pduTrack.getEspduCount());
-        System.out.println(TRACE_PREFIX + "getDefaultWaypointInterval()=" + pduTrack.getDefaultWaypointInterval());
-        System.out.println(TRACE_PREFIX + "getTotalDurationSeconds()="    + pduTrack.getTotalDurationSeconds());
-        
-        System.out.println("=================================");
-        System.out.println("PduTrack pduList marshalling checks");
-        System.out.println("= = = = = = = = = = = = = = = = =");
-        try
-        {
-//          int BYTE_BUFFER_SIZE = 400; // TODO what is expected max buffer size?
-            for (int i = 0; i < TOTAL_PDUS; i++)
-            {
-                Pdu pdu = pduTrack.getPduList().get(i);
-                if (!(pdu instanceof EntityStatePdu))
-                    continue; // skip remainder of this loop
-                EntityStatePdu espdu = (EntityStatePdu) pdu;
-                System.out.println("espdu from pduTrack pduList");
-                reportPdu(espdu);
-                byte[] byteArray = espdu.marshal();
-                System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
-                flushBuffers();
-                
-                ByteBuffer byteBuffer = ByteBuffer.allocate(byteArray.length);
-                espdu.marshal(byteBuffer);
-                System.out.println("espdu.marshal(byteBuffer):                              " + bytesToHex(byteBuffer.array()));
-                flushBuffers();
-                
-                espdu.marshal(dataOutputStream);
-                byte[] byteArrayDOS = byteArrayOutputStream.toByteArray();
-                System.out.println("espdu.marshal(dataOutputStream):                        " + bytesToHex(byteArrayDOS));
-                flushBuffers();
-
-                System.out.println(); // - - - - - - - - - - - - - - - - -
-                
-                System.out.println("espdu.copyByteBuffer()");
-                reportPdu(espdu.copyByteBuffer());
-                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal();
-                System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
-                flushBuffers();
-                
-                ByteBuffer byteBufferCopy = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
-                espdu.copyByteBuffer().marshal(byteBufferCopy);
-                System.out.println("espdu.copyByteBuffer().marshal(byteBufferCopy):         " + bytesToHex(byteBufferCopy.array()));
-                flushBuffers();
-                
-                espdu.copyByteBuffer().marshal(dataOutputStream);
-                byte[] byteArrayDosCopy = byteArrayOutputStream.toByteArray();
-                System.out.println("espdu.copyByteBuffer().marshal(dataOutputStream):       " + bytesToHex(byteArrayDosCopy));
-                flushBuffers();
-
-                System.out.println(); // - - - - - - - - - - - - - - - - -
-                
-                System.out.println("espdu.copyDataOutputStream()");
-                reportPdu(espdu.copyDataOutputStream());
-                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal();
-                System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
-                flushBuffers();
-                
-                ByteBuffer byteBufferCopyDOS = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
-                espdu.copyDataOutputStream().marshal(byteBufferCopyDOS);
-                System.out.println("espdu.copyDataOutputStream().marshal(byteBufferCopy):   " + bytesToHex(byteBufferCopyDOS.array()));
-                flushBuffers();
-                
-                espdu.copyDataOutputStream().marshal(dataOutputStream);
-                byte[] byteArrayDosCopy2 = byteArrayOutputStream.toByteArray();
-                System.out.println("espdu.copyDataOutputStream().marshal(dataOutputStream): " + bytesToHex(byteArrayDosCopy2));
-                flushBuffers();
-                System.out.println();
-                
-                System.out.println("= = = = = = = = = = = = = = = = =");
-            }
-        }
-        catch(Exception e)
-        {
-            System.out.println(TRACE_PREFIX + "Marshalling test exception: " + e.getMessage());
-        }
-        System.out.println("=================================");
-        pduTrack.setAddLineBreaksWithinKeyValues(true);
-        System.out.println(pduTrack.createX3dModel()); // 
-        System.out.println("=================================");
-
-        recordingStop = LocalDateTime.now();
-        System.out.println(TRACE_PREFIX + "selfTest() complete.");
-    }
-    
-    /**
-     * Main method for testing.
-     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
-     * @param args [address, port, descriptor] command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
-     */
-    public static void main(String[] args)
-    {
-        System.out.println("*** PduTrack.main() self test started...");
-          
-        PduTrack pduTrack = new PduTrack();
-        
-        pduTrack.setDescriptor("main() self test");
-        
-        pduTrack.selfTest();
-        
-        System.out.println("*** PduTrack.main() self test complete.");
-    }
-
-}
+/*
+Copyright (c) 1995-2022 held by the author(s).  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer
+      in the documentation and/or other materials provided with the
+      distribution.
+    * Neither the names of the Naval Postgraduate School (NPS)
+      Modeling Virtual Environments and Simulation (MOVES) Institute
+      https://www.nps.edu and https://www.nps.edu/web/moves
+      nor the names of its contributors may be used to endorse or
+      promote products derived from this software without specific
+      prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+// TODO move into opendis7 distribution tree in package edu.nps.moves.dis7.utilities.stream;
+
+package MV3500Cohort2022MayJune.homework2.Tam;
+
+import MV3500Cohort2022MayJune.homework2.Duran.*;
+import edu.nps.moves.dis7.entities.swe.platform.surface._001Poseidon;
+import edu.nps.moves.dis7.enumerations.DisPduType;
+import edu.nps.moves.dis7.enumerations.ForceID;
+import edu.nps.moves.dis7.pdus.EntityID;
+import edu.nps.moves.dis7.pdus.EntityStatePdu;
+import edu.nps.moves.dis7.pdus.EulerAngles;
+import edu.nps.moves.dis7.pdus.Pdu;
+import edu.nps.moves.dis7.pdus.Vector3Double;
+import edu.nps.moves.dis7.utilities.DisTime;
+import edu.nps.moves.dis7.utilities.PduFactory;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Create a track from DIS ESPDUs
+ * @author brutzman
+ */
+public class PduTrack
+{   
+    private String          descriptor = new String();
+    private ArrayList<Pdu>  pduList = new ArrayList<>();
+    private EntityStatePdu  initialEspdu;
+    private EntityStatePdu  latestEspdu;
+    private Vector3Double   initialLocation;
+    private Vector3Double   latestLocation;
+    
+    /** waypoint timelineList in seconds */
+    private ArrayList<Float>          timelineList = new ArrayList<>();
+    private ArrayList<Vector3Double> waypointsList = new ArrayList<>();
+    private ArrayList<EulerAngles> eulerAnglesList = new ArrayList<>();
+    
+    private String                             author = new String();
+    private String                 x3dModelIdentifier = new String();
+    private String                 x3dModelName       = "PduTrackInterpolation.x3d";
+    private float             defaultWaypointInterval = -1;
+    private float                     durationSeconds = -1;
+    private String                   x3dTimeSensorDEF = new String();
+    private String         x3dPositionInterpolatorDEF = new String();
+    private String      x3dOrientationInterpolatorDEF = new String();
+    private boolean      addLineBreaksWithinKeyValues = false;
+    /** what kind of timestamp is being used */
+    public    DisTime.TimestampStyle timestampStyle        = DisTime.TimestampStyle.IEEE_ABSOLUTE;
+    /** direct access to pduFactory for creating new PDU instances */
+    protected PduFactory             pduFactory            = new PduFactory(timestampStyle);
+    private   LocalDateTime          recordingStart;
+    private   LocalDateTime          recordingStop;
+    private   String                 todaysDateString      = new String();
+    
+    /** direct access to byteArrayOutputStream */
+    protected ByteArrayOutputStream  byteArrayOutputStream = new ByteArrayOutputStream();
+    /** direct access to DataOutputStream */
+    protected DataOutputStream       dataOutputStream      = new DataOutputStream(byteArrayOutputStream);
+    private   String TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName()) + "] ";
+    
+    /**
+     * Constructor for PduTrack
+     */
+    public PduTrack()
+    {
+        // initialization code here
+        
+        // https://docs.oracle.com/javase/tutorial/datetime/TOC.html
+        // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/package-summary.html
+        // https://stackoverflow.com/questions/5175728/how-to-get-the-current-date-time-in-java/5175900 (scroll down to java.time)
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy");
+        todaysDateString = LocalDate.now().format(formatter);
+//      System.out.println(TRACE_PREFIX + "today=" + todayString);
+    }
+    
+    /**
+     * Set timestampStyle used by PduFactory
+     * @param newTimestampStyle new value to set
+     */
+    public PduTrack(DisTime.TimestampStyle newTimestampStyle)
+    {
+        timestampStyle = newTimestampStyle;
+        DisTime.setTimestampStyle(newTimestampStyle);
+        // automatic super invocation: return PduTrack();
+    }
+    
+    /**
+     * Get simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
+     * @return simple descriptor name
+     */
+    public String getDescriptor() 
+    {
+        return descriptor;
+    }
+    /**
+     * Set new simple descriptor (such as parent class name) for this SimulationManager, used in trace statements
+     * @param newDescriptor simple descriptor name for this interface
+     * @return same object to permit progressive setters */
+    public PduTrack setDescriptor(String newDescriptor) 
+    {
+        if (newDescriptor != null)
+            this.descriptor = newDescriptor.trim();
+        TRACE_PREFIX = "[" + (PduTrack.class.getSimpleName() + " " + descriptor) + "] ";
+        return this;
+    }
+    /**
+     * Get timestampStyle used by PduFactory
+     * @return current timestampStyle
+     */
+    public DisTime.TimestampStyle getTimestampStyle()
+    {
+        return timestampStyle;
+    }
+    /**
+     * Set timestampStyle used by PduFactory
+     * @param newTimestampStyle the timestampStyle to set
+     * @return same object to permit progressive setters 
+     */
+    public PduTrack setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) 
+    {
+        this.timestampStyle = newTimestampStyle;
+        DisTime.setTimestampStyle(newTimestampStyle);
+        return this;
+    }
+
+    /**
+     * Determine initial location, reset to (0 0 0) if not found
+     * @return current initialLocation
+     */
+    public Vector3Double getInitialLocation() {
+        if (initialLocation == null)
+        {
+            System.out.println (TRACE_PREFIX + "getInitialLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
+            return new Vector3Double();
+        }
+        return initialLocation;
+    }
+    /**
+     * Determine current location, reset to (0 0 0) if not found
+     * @return current latestLocation
+     */
+    public Vector3Double getLatestLocation() {
+        if (latestLocation == null)
+        {
+            System.out.println (TRACE_PREFIX + "getLatestLocation() not found, isTrackEmpty()=" + isTrackEmpty() + ", returning 0 0 0");
+            return new Vector3Double();
+        }
+        return latestLocation;
+    }
+    /**
+     * Get individual Pdu from pduList, index must not exceed existing pduList size
+     * @param index for pdu of interest
+     * @return pdu of interest
+     */
+    public Pdu getPdu(int index) throws IndexOutOfBoundsException
+    {
+        if ((index >= pduList.size()) || (index < 0))
+        {
+            System.out.println (TRACE_PREFIX + "getPdu(" + index + ") out of bounds, pduList.size()=" + pduList.size());
+            // then throw exception
+        }
+        return pduList.get(index);
+    }
+    /**
+     * get current pduList
+     * @return current pduList
+     */
+    public ArrayList<Pdu> getPduList() {
+        return pduList;
+    }
+    /**
+     * get current waypointsList
+     * @return current waypointsList
+     */
+    public ArrayList<Vector3Double> getWaypointsList() {
+        return waypointsList;
+    }
+    /**
+     * current eulerAnglesList
+     * @return current eulerAnglesList
+     */
+    public ArrayList<EulerAngles> getEulerAnglesList() {
+        return eulerAnglesList;
+    }
+    /**
+     * Time in seconds corresponding to each PDU
+     * @return current timelineList
+     */
+    public ArrayList<Float> getTimelineList() {
+        return timelineList;
+    }
+    /**
+     * Add PDU, typically ESPDU
+     * @param newPdu new Pdu to add, typically ESPDU
+     * @return same object to permit progressive setters
+     */
+    public PduTrack addPdu(Pdu newPdu)
+    {
+        if (newPdu.getPduType() == DisPduType.ENTITY_STATE)
+        {
+////          EntityStatePdu deepCopyEspdu = new EntityStatePdu();
+//            EntityStatePdu deepCopyEspdu = pduFactory.makeEntityStatePdu();
+//            deepCopyEspdu.setTimestamp        (((EntityStatePdu)newPdu).getTimestamp());
+//            deepCopyEspdu.setMarking          (((EntityStatePdu)newPdu).getMarking());
+//            deepCopyEspdu.setEntityID         (((EntityStatePdu)newPdu).getEntityID());
+//            deepCopyEspdu.setForceId          (((EntityStatePdu)newPdu).getForceId());
+//            deepCopyEspdu.setEntityType       (((EntityStatePdu)newPdu).getEntityType());
+//            deepCopyEspdu.setEntityLocation   (((EntityStatePdu)newPdu).getEntityLocation());
+//            deepCopyEspdu.setEntityOrientation(((EntityStatePdu)newPdu).getEntityOrientation());
+            
+            EntityStatePdu deepCopyEspdu = ((EntityStatePdu)newPdu).copy();
+            pduList.add(deepCopyEspdu);
+//          alternative trials:
+//          pduList.add(((EntityStatePdu)newPdu).copyDataOutputStream());
+//          pduList.add(((EntityStatePdu)newPdu).copy());
+            
+            if (initialLocation == null)
+            {
+                initialEspdu    = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
+                initialLocation = deepCopyEspdu.getEntityLocation();
+            }
+            latestEspdu         = deepCopyEspdu; // must save object since pduList might contain more than ESPDUs
+            latestLocation      = deepCopyEspdu.getEntityLocation();
+        }
+        else pduList.add(newPdu); // TODO copy() - careful, must be a new object and not a reference
+        return this;
+    }
+    /**
+     * clear all PDUs
+     * @return same object to permit progressive setters
+     */
+    public PduTrack clearPduLists()
+    {
+           getPduList().clear();
+          waypointsList.clear();
+        eulerAnglesList.clear();
+           timelineList.clear();
+           initialEspdu = null;
+            latestEspdu = null;
+        initialLocation = null;
+         latestLocation = null;
+        return this;
+    }
+    
+    private String normalizeNameToken(String candidateDEF)
+    {
+        return candidateDEF.replace(" ", "").replace("-", "")
+                           .replace("(", "").replace(")", "")
+                           .replace("[", "").replace("])", "");
+    }
+
+    /**
+     * Provide DEF value if not defined by program
+     * @return current x3dTimeSensorDEF
+     */
+    public String getX3dTimeSensorDEF() {
+        if    (x3dTimeSensorDEF.isEmpty())
+               x3dTimeSensorDEF = normalizeNameToken(getDescriptor()) + "Clock";
+        return x3dTimeSensorDEF;
+    }
+
+    /**
+     * Set DEF value for X3D node
+     * @param x3dTimeSensorDEF the x3dTimeSensorDEF to set
+     */
+    public void setX3dTimeSensorDEF(String x3dTimeSensorDEF) {
+        this.x3dTimeSensorDEF = normalizeNameToken(x3dTimeSensorDEF);
+    }
+
+    /**
+     * Provide DEF value if not defined by program
+     * @return current x3dPositionInterpolatorDEF
+     */
+    public String getX3dPositionInterpolatorDEF() {
+        if    (x3dPositionInterpolatorDEF.isEmpty())
+               x3dPositionInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Positions";
+        return x3dPositionInterpolatorDEF;
+    }
+
+    /**
+     * Set DEF value for X3D node
+     * @param x3dPositionInterpolatorDEF the x3dPositionInterpolatorDEF to set
+     */
+    public void setX3dPositionInterpolatorDEF(String x3dPositionInterpolatorDEF) {
+        this.x3dPositionInterpolatorDEF = normalizeNameToken(x3dPositionInterpolatorDEF);
+    }
+
+    /**
+     * Provide DEF value if not defined by program
+     * @return current x3dOrientationInterpolatorDEF
+     */
+    public String getX3dOrientationInterpolatorDEF() {
+        if    (x3dOrientationInterpolatorDEF.isEmpty())
+               x3dOrientationInterpolatorDEF = normalizeNameToken(getDescriptor()) + "Orientations";
+        return x3dOrientationInterpolatorDEF;
+    }
+
+    /**
+     * Set DEF value for X3D node
+     * @param x3dOrientationInterpolatorDEF the x3dOrientationInterpolatorDEF to set
+     */
+    public void setX3dOrientationInterpolatorDEF(String x3dOrientationInterpolatorDEF) {
+        this.x3dOrientationInterpolatorDEF = normalizeNameToken(x3dOrientationInterpolatorDEF);
+    }
+
+    /**
+     * Sort all PDUs by timestamp
+     * @see <a href="https://stackoverflow.com/questions/16252269/how-to-sort-an-arraylist">StackOverflow: How to sort an ArrayList?</a>
+     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
+     * @return same object to permit progressive setters
+     */
+    public PduTrack sortPdus()
+    {
+        Collections.sort(pduList, new Comparator<Pdu>() {
+            @Override
+            public int compare(Pdu lhs, Pdu rhs)
+            {
+                // -1 less than, 1 greater than, 0 equal
+                if      (lhs.occursBefore(rhs))
+                         return -1;
+                else if (lhs.occursSameTime(rhs))
+                         return 0;
+                else     return 1;
+            }
+        });
+        return this;
+    }
+    /**
+     * Reverse order of PDU list
+     * @see <a href="https://stackoverflow.com/questions/10766492/what-is-the-simplest-way-to-reverse-an-arraylist">StackOverflow: What is the Simplest Way to Reverse an ArrayList?</a>
+     * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/doc-files/coll-overview.html">Collections Framework Overview</a>
+     * @return same object to permit progressive setters
+     */
+    public PduTrack reversePdus()
+    {
+        Collections.reverse(pduList);
+        return this;
+    }
+
+    /**
+     * Determine whether any ESPDUs have been received by this track
+     * @return whether track is empty
+     */
+    public boolean isTrackEmpty()
+    {
+        return (getEspduCount() == 0);
+    }
+    /**
+     * Count ESPDUs in pduList
+     * @return number of ESPDUs in pduList
+     */
+    public int getEspduCount()
+    {
+        int counter = 0;
+        for (Pdu nextPdu : getPduList())
+        {
+            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
+               counter += 1;
+        }
+        return counter;
+    }
+    /**
+     * Compute track duration in timestamp ticks
+     * @return duration in timestamp ticks between initial and final ESPDU timestamps in waypointList
+     */
+    public int getTotalDurationTicks()
+    {
+        int    initialTime = -1;
+        int      finalTime = -1;
+        int  durationTicks = -1; // used if pduList is empty
+        
+        // must skip through pduList since non-ESPDU PDUs may be present
+        for (Pdu nextPdu : getPduList())
+        {
+            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
+            {
+                if (initialTime == -1)
+                    initialTime = nextPdu.getTimestamp();
+                finalTime = nextPdu.getTimestamp();
+            }
+        }
+        if ((initialTime >= 0) && (finalTime >= 0))
+             durationTicks = (finalTime - initialTime);
+        if (getPduList().isEmpty())
+        {
+            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to empty pdu list");
+        }
+        else if ((durationTicks <= 0) && (defaultWaypointInterval <= 0))
+        {
+            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to illegal pdu list");
+        }
+        return durationTicks;
+    }
+    /**
+     * Compute track duration in seconds
+     * @return duration in seconds between initial and final ESPDU timestamps in waypointList
+     */
+    public float getTotalDurationSeconds()
+    {
+        if (defaultWaypointInterval > 0)
+        {
+            return getEspduCount() * defaultWaypointInterval;
+        }
+        else if (getTotalDurationTicks() < 0)
+               durationSeconds = getTotalDurationTicks() * 1.0f; // TODO convert
+        return durationSeconds;
+    }
+    /**
+     * Create waypoints and angles using all ESPDU points, with no linear regression or array reduction.
+     * @return same object to permit progressive setters
+     */
+    public PduTrack createRawWaypoints()
+    {
+        // https://stackoverflow.com/questions/6536094/java-arraylist-copy
+        
+           timelineList.clear();
+          waypointsList.clear();
+        eulerAnglesList.clear();
+        float clock = 0.0f;
+        for (int i = 0; i < pduList.size(); i++)
+        {
+            Pdu nextPdu = pduList.get(i);
+            if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
+            {
+                EntityStatePdu espdu = (EntityStatePdu)nextPdu;
+                if (defaultWaypointInterval > 0)
+                {
+                    timelineList.add(clock);
+                    clock += defaultWaypointInterval;
+                }
+                else
+                {
+                    timelineList.add(espdu.getTimestamp() * 1.0f); // TODO convert
+                }
+                   waypointsList.add(espdu.getEntityLocation());
+                 eulerAnglesList.add(espdu.getEntityOrientation());
+            }
+        }
+        return this;
+    }
+    /** 
+     * Utility method to create TimeSensor
+     * @return TimeSensor string in XML format
+     */
+    public String createX3dTimeSensorString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    <TimeSensor");
+        sb.append(" DEF='").append(getX3dTimeSensorDEF()).append("'");
+        sb.append(" cycleInterval='").append(String.valueOf(getTotalDurationSeconds())).append("'");
+        sb.append(" loop='true'");
+        sb.append("/>").append("\n");
+        
+        return sb.toString();
+    }
+    /**
+     * Create PositionInterpolator from Pdu list
+     * @return X3D PositionInterpolator as string
+     */
+    public String createX3dPositionInterpolatorString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    <PositionInterpolator");
+        sb.append(" DEF='").append(getX3dPositionInterpolatorDEF()).append("'");
+        sb.append(" key='");
+        for (int i = 0; i < timelineList.size(); i++)
+        {
+            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
+            sb.append(String.valueOf(nextDuration));
+            if (i < timelineList.size() - 1)
+                sb.append(" ");
+        }
+        sb.append("'");
+        sb.append(" keyValue='");
+        for (int i = 0; i < waypointsList.size(); i++)
+        {
+            if (hasAddLineBreaksWithinKeyValues())
+                sb.append("\n");
+            Vector3Double nextPosition = waypointsList.get(i);
+            sb.append(String.valueOf(nextPosition.getX())).append(" ")
+              .append(String.valueOf(nextPosition.getY())).append(" ")
+              .append(String.valueOf(nextPosition.getZ()));
+            if (i < waypointsList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'");
+        sb.append("/>").append("\n");
+        
+        return sb.toString();
+    }
+    /**
+     * Create OrientationInterpolator from Pdu list
+     * TODO preliminary support only includes horizontal rotation.
+     * @return X3D OrientationInterpolator as string
+     */
+    public String createX3dOrientationInterpolatorString()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    <OrientationInterpolator");
+        sb.append(" DEF='").append(getX3dOrientationInterpolatorDEF()).append("'");
+        sb.append(" key='");
+        for (int i = 0; i < timelineList.size(); i++)
+        {
+            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
+            sb.append(String.valueOf(nextDuration));
+            if (i < timelineList.size() - 1)
+                sb.append(" ");
+        }
+        sb.append("'");
+        sb.append(" keyValue='");
+        for (int i = 0; i < eulerAnglesList.size(); i++)
+        {
+            if (hasAddLineBreaksWithinKeyValues())
+                sb.append("\n");
+            EulerAngles nextEulerAngle = new EulerAngles();
+            float axisX = 0.0f;
+            float axisY = 1.0f;
+            float axisZ = 0.0f;
+            float angle = 0.0f; // radians
+            
+            nextEulerAngle = eulerAnglesList.get(i);
+            angle = nextEulerAngle.getTheta();
+            
+            sb.append(String.valueOf(axisX)).append(" ")
+              .append(String.valueOf(axisY)).append(" ")
+              .append(String.valueOf(axisZ)).append(" ")
+              .append(String.valueOf(angle));
+            if (i < eulerAnglesList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'");
+        sb.append("/>").append("\n");
+        
+        return sb.toString();
+    }
+
+    /**
+     * Get name of author used as creator of X3D model
+     * @return current author
+     */
+    public String getAuthor() {
+        return author;
+    }
+
+    /**
+     * Set name of author used as creator of X3D model
+     * @param author the author to set
+     */
+    public void setAuthor(String author) {
+        if  (author == null)
+             author = new String();
+        author = author.trim();
+        this.author = author;
+    }
+
+    /**
+     * Get name of online url identifier for X3D model
+     * @return current x3dModelIdentifier
+     */
+    public String getX3dModelIdentifier() {
+        return x3dModelIdentifier;
+    }
+
+    /**
+     * Set name of online url identifier for X3D model
+     * @param x3dModelIdentifier the x3dModelIdentifier to set
+     */
+    public void setX3dModelIdentifier(String x3dModelIdentifier) {
+        if  (x3dModelIdentifier == null)
+             x3dModelIdentifier = new String();
+        x3dModelIdentifier = x3dModelIdentifier.trim();
+        if (!x3dModelIdentifier.startsWith("http://") && !x3dModelIdentifier.startsWith("https://"))
+            System.out.println(TRACE_PREFIX + "warning, identifier typically begins with https:// or http://");
+        else this.x3dModelIdentifier = x3dModelIdentifier;
+    }
+
+    /**
+     * File name for X3D model production
+     * @return current x3dModelName
+     */
+    public String getX3dModelName() {
+        return x3dModelName;
+    }
+    /**
+     * File name for X3D model production
+     * @param x3dModelName the x3dModelName to set
+     */
+    public void setX3dModelName(String x3dModelName) {
+        if  (x3dModelName == null)
+             x3dModelName = new String();
+        x3dModelName = x3dModelName.trim();
+        this.x3dModelName = x3dModelName;
+    }
+
+    /**
+     * Verbose (but more readable) output of numeric arrays in X3D model
+     * @return current addLineBreaksWithinKeyValues
+     */
+    public boolean hasAddLineBreaksWithinKeyValues() {
+        return addLineBreaksWithinKeyValues;
+    }
+
+    /**
+     * Verbose (but more readable) output of numeric arrays in X3D model
+     * @param addLineBreaksWithinKeyValues the addLineBreaksWithinKeyValues to set
+     */
+    public void setAddLineBreaksWithinKeyValues(boolean addLineBreaksWithinKeyValues) {
+        this.addLineBreaksWithinKeyValues = addLineBreaksWithinKeyValues;
+    }
+    /**
+     * Create full X3D interpolator model from Pdu list, assembling sections of scene graph
+     * @return X3D model as string
+     */
+    public String createX3dModel()
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(createX3dModelHeaderString());
+        sb.append(createX3dTimeSensorString());
+        sb.append(createX3dPositionInterpolatorString());
+        sb.append(createX3dOrientationInterpolatorString());
+        sb.append(createX3dRoutesGeometryFooterString());
+        return sb.toString();
+    }
+    /**
+     * Create PositionInterpolator from Pdu list
+     * @return X3D PositionInterpolator as string
+     */
+    public String createX3dModelHeaderString()
+    {        
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("\n");
+        sb.append("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 4.0//EN\" \"https://www.web3d.org/specifications/x3d-4.0.dtd\">").append("\n");
+        sb.append("<X3D profile='Interchange' version='4.0' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-4.0.xsd'>").append("\n");
+        sb.append("  <head>").append("\n");
+        if (!getX3dModelName().isEmpty())
+            sb.append("    <meta content='").append(getX3dModelName()).append("' name='title'/>").append("\n");
+        sb.append("    <meta content='Conversion of ESPDU track into X3D animation interpolators and LineSet.' name='description'/>").append("\n");
+        
+        sb.append("    <meta content='1 January 2022' name='created'/>").append("\n");
+        sb.append("    <meta content='").append(todaysDateString).append("' name='modified'/>").append("\n");
+        if (!getAuthor().isEmpty())
+            sb.append("    <meta content='").append(getAuthor()).append("' name='creator'/>").append("\n");
+        if (!getX3dModelIdentifier().isEmpty())
+            sb.append("    <meta content='").append(getX3dModelIdentifier()).append("' name='identifier'/>").append("\n");
+        
+        sb.append("    <meta content='PduTrack utility, opendis7-java Library https://github.com/open-dis/opendis7-java' name='generator'/>").append("\n");
+        sb.append("    <meta content='NPS MOVES MV3500 Networked Graphics https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Resources https://www.web3d.org/x3d/content/examples/X3dResources.html' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Scene Authoring Hints https://www.web3d.org/x3d/content/examples/X3dSceneAuthoringHints.html' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Tooltips https://www.web3d.org/x3d/tooltips/X3dTooltips.html' name='reference'/>").append("\n");
+        sb.append("    <meta content='X3D Validator https://savage.nps.edu/X3dValidator' name='reference'/>").append("\n");
+        sb.append("    <meta content='Open source https://raw.githubusercontent.com/open-dis/opendis7-java/master/license.html' name='license'/>").append("\n");
+        sb.append("  </head>").append("\n");
+        sb.append("  <Scene>").append("\n");
+        sb.append("    <WorldInfo title='PduTrackInterpolation.x3d'/>").append("\n");
+
+        return sb.toString(); 
+    }
+    /**
+     * Create X3D ROUTEs and footer to connect TimeSensor to interpolators
+     * @return X3D PositionInterpolator as string
+     */
+    public String createX3dRoutesGeometryFooterString()
+    {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
+          .append(getX3dTimeSensorDEF())
+          .append("' toField='set_fraction' toNode='")
+          .append(getX3dPositionInterpolatorDEF())
+          .append("'/>").append("\n");
+        sb.append("    <ROUTE fromField='fraction_changed' fromNode='")
+          .append(getX3dTimeSensorDEF())
+          .append("' toField='set_fraction' toNode='")
+          .append(getX3dOrientationInterpolatorDEF())
+          .append("'/>").append("\n");
+        
+        sb.append("    <Shape>").append("\n");
+        sb.append("      <Appearance DEF='TrackAppearance'>").append("\n");
+        sb.append("        <Material emissiveColor='0.2 0.8 0.8'/>").append("\n");
+        sb.append("      </Appearance>").append("\n");
+        sb.append("      <LineSet vertexCount='").append(waypointsList.size()).append("'>").append("\n");
+        sb.append("        <Coordinate point='");
+        for (int i = 0; i < waypointsList.size(); i++)
+        {
+            if (hasAddLineBreaksWithinKeyValues())
+                sb.append("\n");
+            Vector3Double nextPosition = waypointsList.get(i);
+            sb.append(String.valueOf(nextPosition.getX())).append(" ")
+              .append(String.valueOf(nextPosition.getY())).append(" ")
+              .append(String.valueOf(nextPosition.getZ()));
+            if (i < waypointsList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'/>").append("\n");
+        sb.append("      </LineSet>").append("\n");
+        sb.append("    </Shape>").append("\n");
+        sb.append("    <Transform DEF='AnimationTransform'>").append("\n");
+        sb.append("      <Transform rotation='0 0 1 1.57'>").append("\n");
+        sb.append("        <Shape>").append("\n");
+        sb.append("          <Appearance USE='TrackAppearance'/>").append("\n");
+        sb.append("          <Cone bottomRadius='0.5'/>").append("\n");
+        sb.append("        </Shape>").append("\n");
+        sb.append("      </Transform>").append("\n");
+        sb.append("    </Transform>").append("\n");
+        sb.append("    <ROUTE fromField='value_changed' fromNode='")
+          .append(getX3dPositionInterpolatorDEF())
+          .append("' toField='translation' toNode='AnimationTransform'/>").append("\n");
+        sb.append("    <ROUTE fromField='value_changed' fromNode='")
+          .append(getX3dOrientationInterpolatorDEF())
+          .append("' toField='rotation' toNode='AnimationTransform'/>").append("\n");
+        
+        sb.append("  </Scene>").append("\n");
+        sb.append("</X3D>").append("\n");
+        
+        return sb.toString();
+    }
+
+    /**
+     * get defaultWaypointInterval
+     * @return current wayPointInterval
+     */
+    public float getDefaultWaypointInterval() {
+        return defaultWaypointInterval;
+    }
+
+    /**
+     * Set uniform waypoint interval (currently for testing)
+     * @param newWaypointInterval the wayPointInterval to set, in seconds, must be greater than zero
+     * @return same object to permit progressive setters */
+    public PduTrack setDefaultWaypointInterval(float newWaypointInterval) {
+        if (newWaypointInterval > 0.0)
+            this.defaultWaypointInterval = newWaypointInterval;
+        else 
+        {
+            System.out.println(TRACE_PREFIX + "error in setWaypointInterval(newWaypointInterval=" + newWaypointInterval + ") must be greater than zero");
+            return this;
+        }
+            
+        float clock = 0.0f;
+        if (!timelineList.isEmpty())
+        {   
+            ArrayList<Float> newTimelineList = new ArrayList<>();
+            for (int i = 0; i < getEspduCount(); i++)
+            {
+                newTimelineList.add(clock);
+                clock += defaultWaypointInterval;
+            }
+            timelineList = newTimelineList; // TO Array copy?
+        }
+        return this;
+    }
+    /** whether or not to insert commas between hex values */
+    private boolean insertCommas = true;
+    /**
+     * determine whether comma insertion is turned on
+     * @return whether or not to insert commas between hex values
+     */
+    public boolean hasInsertCommas() {
+        return insertCommas;
+    }
+    /**
+     * set whether comma insertion is turned on
+     * @param insertCommas the insertCommas value to set
+     */
+    public void setInsertCommas(boolean insertCommas) {
+        this.insertCommas = insertCommas;
+    }
+    /**
+     * Convert byte array to hex string
+     * @param bytes input data
+     * @param insertCommas whether to insert commas between hex values
+     * @return hex string
+     */
+    public String bytesToHex(byte[] bytes, boolean insertCommas)
+    {
+        this.setInsertCommas(insertCommas);
+        return bytesToHex(bytes);
+    }
+    /**
+     * Convert byte array to hex string
+     * @param bytes input data
+     * @see <a href="https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java">https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java</a>
+     * @return hex string
+     */
+    public static String bytesToHex(byte[] bytes)
+    {
+        boolean insertCommas = true;
+        final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+        char[] hexChars = new char[bytes.length * 2];
+        StringBuilder sb = new StringBuilder();
+        for (int j = 0; j < bytes.length; j++) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2]     = HEX_ARRAY[v >>> 4];
+            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
+//          if (!(hexChars[j * 2] == '0')) // omit leading zero
+            sb.append(hexChars[j * 2]);
+            sb.append(hexChars[j * 2 + 1]);
+            if (insertCommas && (j < bytes.length - 1))
+                sb.append(", ");
+        }
+        return sb.toString();
+    }
+    /**
+     * Report current PDU information to console
+     * @param anEspdu EntityStatePdu of interest
+     * @return same object to permit progressive setters
+     */
+    public PduTrack reportPdu(EntityStatePdu anEspdu)
+    {
+        System.out.println (
+                String.format("%s", anEspdu.getMarkingString().trim()) + ", " +
+                DisTime.convertToString(anEspdu.getTimestamp()) + " (" +
+                String.format("%08d", anEspdu.getTimestamp()) + "), " + 
+                "EntityID=(" + 
+                anEspdu.getEntityID().getSiteID() + ", " +
+                anEspdu.getEntityID().getApplicationID() + ", " + 
+                anEspdu.getEntityID().getEntityID() + "), " +
+                "location=(" + 
+                String.format("%4.1f", anEspdu.getEntityLocation().getX()) + ", " +
+                String.format("%4.1f", anEspdu.getEntityLocation().getY()) + ", " + 
+                String.format("%4.1f", anEspdu.getEntityLocation().getZ()) + ")"
+//              + " " + espdu_1.getEntityLinearVelocity().toString()
+        );
+        return this;
+    }
+    
+    /** Flush all buffers to reduce console scrambling while threaded
+     */
+    protected void flushBuffers()
+    {
+        try
+        {
+                 dataOutputStream.flush(); 
+            byteArrayOutputStream.flush();
+            byteArrayOutputStream.reset();
+            System.err.flush(); 
+            System.out.flush();
+        }
+        catch (IOException ioe)
+        {
+            System.out.println(TRACE_PREFIX + "flushBuffers() IOException: " + ioe.getMessage());
+        }
+    }
+    
+    /** Self test to check basic operation, invoked by main()
+     */
+    @SuppressWarnings("SleepWhileInLoop")
+    public void selfTest()
+    {
+        final int TOTAL_PDUS = 5;
+        System.out.println(TRACE_PREFIX + "selfTest() start...");
+      
+        PduTrack pduTrack = new PduTrack();
+        pduTrack.setDescriptor("PduTrack Self Test");
+        pduTrack.setAuthor("Don Brutzman");
+        pduTrack.setX3dModelIdentifier("https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/PduTrackInterpolation.x3d");
+        pduTrack.setDefaultWaypointInterval(1.0f); // experimentation with timestamp values
+
+        DisTime.setEpochLvcNow();
+        recordingStart = LocalDateTime.now();
+        Instant epoch = DisTime.getEpochLvc();
+        System.out.println(TRACE_PREFIX + "DisTime.hasEpochLvc()=" + DisTime.hasEpochLvc() + 
+                                         ", DisTime.getEpochLvc()=" + epoch + 
+                                         ", Instant.now()=" + Instant.now());
+        
+        EntityID entityID_123 = new EntityID();
+        entityID_123.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID;
+        // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
+
+        for (int i = 0; i < TOTAL_PDUS; i++) // create espdus and add each to track pduList
+        {
+//          EntityStatePdu espdu = new EntityStatePdu();
+            EntityStatePdu espdu = pduFactory.makeEntityStatePdu(); // TODO check Pdu.Type
+            espdu.setTimestamp(DisTime.getCurrentDisTimestamp()); // chooses appropriate version
+            espdu.setMarking("ESPDU " + i);
+            espdu.setEntityLocation(i, i, i);
+            espdu.setEntityOrientation(0, (float)(45.0 * Math.PI / 180.0), 0);
+            espdu.setEntityID(entityID_123);
+            espdu.setForceId(ForceID.FRIENDLY);
+            espdu.setEntityType(new _001Poseidon()); // note import statement above
+            pduTrack.addPdu(espdu); // create copy
+            reportPdu(espdu);
+            try
+            {
+                Thread.sleep(100l);
+            }
+            catch (InterruptedException ie)
+            {
+                System.out.println(TRACE_PREFIX + "exceptional sleep dulay when generating ESPDUs: " + ie.getMessage());
+            }
+        }
+//        System.out.println(TRACE_PREFIX + "reversePdus() then sortPdus() to test track operations");
+//        pduTrack.reversePdus(); // test
+//        pduTrack.sortPdus();    // restore
+        pduTrack.createRawWaypoints(); // copies all ESPDU points to waypoints
+        
+        System.out.println(TRACE_PREFIX + "getEspduCount()="              + pduTrack.getEspduCount());
+        System.out.println(TRACE_PREFIX + "getDefaultWaypointInterval()=" + pduTrack.getDefaultWaypointInterval());
+        System.out.println(TRACE_PREFIX + "getTotalDurationSeconds()="    + pduTrack.getTotalDurationSeconds());
+        
+        System.out.println("=================================");
+        System.out.println("PduTrack pduList marshalling checks");
+        System.out.println("= = = = = = = = = = = = = = = = =");
+        try
+        {
+//          int BYTE_BUFFER_SIZE = 400; // TODO what is expected max buffer size?
+            for (int i = 0; i < TOTAL_PDUS; i++)
+            {
+                Pdu pdu = pduTrack.getPduList().get(i);
+                if (!(pdu instanceof EntityStatePdu))
+                    continue; // skip remainder of this loop
+                EntityStatePdu espdu = (EntityStatePdu) pdu;
+                System.out.println("espdu from pduTrack pduList");
+                reportPdu(espdu);
+                byte[] byteArray = espdu.marshal().array();
+                System.out.println("espdu.marshal() byteArray:                              " + bytesToHex(byteArray));
+                flushBuffers();
+                
+                ByteBuffer byteBuffer = ByteBuffer.allocate(byteArray.length);
+                espdu.marshal(byteBuffer);
+                System.out.println("espdu.marshal(byteBuffer):                              " + bytesToHex(byteBuffer.array()));
+                flushBuffers();
+                
+                espdu.marshal(dataOutputStream);
+                byte[] byteArrayDOS = byteArrayOutputStream.toByteArray();
+                System.out.println("espdu.marshal(dataOutputStream):                        " + bytesToHex(byteArrayDOS));
+                flushBuffers();
+
+                System.out.println(); // - - - - - - - - - - - - - - - - -
+                
+                System.out.println("espdu.copyByteBuffer()");
+                reportPdu(espdu.copyByteBuffer());
+                byte[] byteArrayCopy = espdu.copyByteBuffer().marshal().array();
+                System.out.println("espdu.copyByteBuffer().marshal() byteArray:             " + bytesToHex(byteArrayCopy));
+                flushBuffers();
+                
+                ByteBuffer byteBufferCopy = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
+                espdu.copyByteBuffer().marshal(byteBufferCopy);
+                System.out.println("espdu.copyByteBuffer().marshal(byteBufferCopy):         " + bytesToHex(byteBufferCopy.array()));
+                flushBuffers();
+                
+                espdu.copyByteBuffer().marshal(dataOutputStream);
+                byte[] byteArrayDosCopy = byteArrayOutputStream.toByteArray();
+                System.out.println("espdu.copyByteBuffer().marshal(dataOutputStream):       " + bytesToHex(byteArrayDosCopy));
+                flushBuffers();
+
+                System.out.println(); // - - - - - - - - - - - - - - - - -
+                
+                System.out.println("espdu.copyDataOutputStream()");
+                reportPdu(espdu.copyDataOutputStream());
+                byte[] byteArrayCopyDOS = espdu.copyDataOutputStream().marshal().array();
+                System.out.println("espdu.copyDataOutputStream().marshal() byteArray:       " + bytesToHex(byteArrayCopyDOS));
+                flushBuffers();
+                
+                ByteBuffer byteBufferCopyDOS = ByteBuffer.allocate(byteArray.length); // TODO is there a better way to reset?
+                espdu.copyDataOutputStream().marshal(byteBufferCopyDOS);
+                System.out.println("espdu.copyDataOutputStream().marshal(byteBufferCopy):   " + bytesToHex(byteBufferCopyDOS.array()));
+                flushBuffers();
+                
+                espdu.copyDataOutputStream().marshal(dataOutputStream);
+                byte[] byteArrayDosCopy2 = byteArrayOutputStream.toByteArray();
+                System.out.println("espdu.copyDataOutputStream().marshal(dataOutputStream): " + bytesToHex(byteArrayDosCopy2));
+                flushBuffers();
+                System.out.println();
+                
+                System.out.println("= = = = = = = = = = = = = = = = =");
+            }
+        }
+        catch(Exception e)
+        {
+            System.out.println(TRACE_PREFIX + "Marshalling test exception: " + e.getMessage());
+        }
+        System.out.println("=================================");
+        pduTrack.setAddLineBreaksWithinKeyValues(true);
+        System.out.println(pduTrack.createX3dModel()); // 
+        System.out.println("=================================");
+
+        recordingStop = LocalDateTime.now();
+        System.out.println(TRACE_PREFIX + "selfTest() complete.");
+    }
+    
+    /**
+     * Main method for testing.
+     * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a>
+     * @param args [address, port, descriptor] command-line arguments are an array of optional String parameters that are passed from execution environment during invocation
+     */
+    public static void main(String[] args)
+    {
+        System.out.println("*** PduTrack.main() self test started...");
+          
+        PduTrack pduTrack = new PduTrack();
+        
+        pduTrack.setDescriptor("main() self test");
+        
+        pduTrack.selfTest();
+        
+        System.out.println("*** PduTrack.main() self test complete.");
+    }
+
+}
diff --git a/assignments/src/MV3500Cohort2023MarchJune/homework3/Chojnacki/ExampleSimulationProgram.java b/assignments/src/MV3500Cohort2023MarchJune/homework3/Chojnacki/ExampleSimulationProgram.java
index 8b3bd05a31393d3281e2d1a682bf1fc6d7af64f4..3ac933bf2a488fbef3641ddb99c38e0ef8827db5 100644
--- a/assignments/src/MV3500Cohort2023MarchJune/homework3/Chojnacki/ExampleSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2023MarchJune/homework3/Chojnacki/ExampleSimulationProgram.java
@@ -1,518 +1,518 @@
-/**
- * Copyright (c) 2008-2021, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
- * This work is provided under a BSD open-source license, see project license.html and license.txt
- *
- * This Program is a modified version of ExampleSimulationProgram in order to see the
- * verbose plain text pdu log. This simulates a firewall identifying a malicious packet
- * and tracing its source before destroying it.
- * 
- * and tracing its source before destroying it - updated September 12, 2021 to ensure
- * correct file pushed to Gitlab.
- *
- * @author Bruce Chojnacki
- */
-package MV3500Cohort2023MarchJune.homework3.Chojnacki;
-
-import edu.nps.moves.dis7.enumerations.*; // match any
-import edu.nps.moves.dis7.pdus.*;         // match any of the PDU classes, easier than listing individually
-import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface;
-import edu.nps.moves.dis7.utilities.PduFactory;
-import edu.nps.moves.dis7.utilities.stream.PduRecorder;
-import java.util.ArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * The purpose of this program is to provide an easily modifiable example
- * simulation program that includes DIS-capable entities doing tasks and
- * reporting them to the network. Default settings include PDU recording turned
- * on by default.
- */
-public class ExampleSimulationProgram {
-
-    private boolean verboseComments = true;
-    static final String NETWORK_ADDRESS_DEFAULT = "239.1.2.3";
-    static final int NETWORK_PORT_DEFAULT = 3000;
-    static String networkAddress = NETWORK_ADDRESS_DEFAULT;
-    static int networkPort = NETWORK_PORT_DEFAULT;
-    String DEFAULT_OUTPUT_DIRECTORY = "./pduLog";
-
-    private EntityID createFriendFireWall() {
-        EntityID FNFireWallID = new EntityID(); // 1.1.225.1.1.1 Platform,Cyber,USA,FireWall
-        FNFireWallID.setSiteID(13);
-        FNFireWallID.setApplicationID(43);
-        FNFireWallID.setEntityID(103);
-        return FNFireWallID;
-    }
-
-    private EntityType createFriendFireWallType() {
-        EntityType FNFireWallType = new EntityType();
-        FNFireWallType.setEntityKind(EntityKind.PLATFORM);
-        FNFireWallType.setDomain(Domain.inst(PlatformDomain.OTHER));
-        FNFireWallType.setCountry(Country.UNITED_STATES_OF_AMERICA_USA);
-        FNFireWallType.setCategory(3);
-        FNFireWallType.setSubCategory(1);
-        FNFireWallType.setSpecific(1);
-        return FNFireWallType;
-    }
-
-    private EntityID createMalPacket() {
-        EntityID MalPacketID = new EntityID(); // 1.1.45.1.7.1 Platform,Cyber,China,MaliciousPacket
-        MalPacketID.setSiteID(66);
-        MalPacketID.setApplicationID(666);
-        MalPacketID.setEntityID(6666);
-        return MalPacketID;
-    }
-
-    private EntityType createMalPacketType() {
-        EntityType MalPacketType = new EntityType();
-        MalPacketType.setEntityKind(EntityKind.PLATFORM);
-        MalPacketType.setDomain(Domain.inst(PlatformDomain.OTHER));
-        MalPacketType.setCountry(Country.CHINA_PEOPLES_REPUBLIC_OF_CHN);
-        MalPacketType.setCategory(3);
-        MalPacketType.setSubCategory(1);
-        MalPacketType.setSpecific(1);
-        return MalPacketType;
-    }
-
-    private MunitionDescriptor createTraceroute() {
-
-        EntityType TracerouteType = new EntityType(); //2.2.225.2.13.1
-        TracerouteType.setEntityKind(EntityKind.MUNITION);
-        TracerouteType.setDomain(Domain.inst(PlatformDomain.OTHER));
-        TracerouteType.setCountry(Country.UNITED_STATES_OF_AMERICA_USA);
-        TracerouteType.setCategory(2);
-        TracerouteType.setSubCategory(8);
-        TracerouteType.setSpecific(1);
-        MunitionDescriptor Traceroute = new MunitionDescriptor();
-        Traceroute.setMunitionType(TracerouteType);
-        Traceroute.setQuantity(1000);
-        Traceroute.setFuse(MunitionDescriptorFuse.CONTACT);
-        Traceroute.setRate(200);
-        return Traceroute;
-    }
-
-    /**
-     * This runSimulationLoops() method is for you, a programmer-modifiable
-     * method for defining and running a new simulation of interest. Welcome!
-     * Other parts of this program handle bookkeeping and plumbing tasks so that
-     * you can focus on your model entities and activities. Expandable support
-     * includes DIS EntityStatePdu, FirePdu and CommentPdu all available for
-     * modification and sending in a simulation loop. Continuous improvement
-     * efforts seek to make this program as easy and straightforward as possible
-     * for DIS simulationists to use and adapt. All of the other methods are
-     * setup, teardown and configuration that you may find interesting, even
-     * helpful, but don't really have to worry about.
-     */
-    @SuppressWarnings("SleepWhileInLoop") // yes we do that
-    public void runSimulationLoops() {
-        try {
-            /**
-             * seconds for real-time execution (not simulation time, which may
-             * or may not be the same)
-             */
-            final double SIMULATION_LOOP_DURATION_SECONDS = 1.0;
-            final int SIMULATION_MAX_LOOP_COUNT = 15; // be deliberate out out there!  also avoid infinite loops.
-            int simulationLoopCount = 0;        // variable, initialized at 0
-            boolean simulationComplete = false;     // sentinel variable as termination condition,, are we done yet?
-            boolean fireBool = false;
-            boolean destBool = false;
-
-            // TODO reset clock to zero each time for consistent outputs
-            // Your model setup: define participants.  who's who in this zoo?
-            // Assuming you keep track of entity objects...  here is some support for for Entity 1.
-            // create PDU object for US Firewall and set its values.
-            //EntityID       entityID_1    = new EntityID();
-            //entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID; 
-            EntityStatePdu entityStatePdu_1 = pduFactory.makeEntityStatePdu();
-            entityStatePdu_1.setEntityID(createFriendFireWall());
-            entityStatePdu_1.setEntityType(createFriendFireWallType());
-            entityStatePdu_1.getEntityLocation().setX(0);
-            entityStatePdu_1.setForceId(ForceID.FRIENDLY);
-
-            // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
-            // create PDU object for Malware Packet and set its values.
-            //EntityID       entityID_2    = new EntityID();
-            //entityID_2.setSiteID(4).setApplicationID(5).setEntityID(6);
-            EntityStatePdu entityStatePdu_2 = pduFactory.makeEntityStatePdu();
-            entityStatePdu_2.setEntityID(createMalPacket());
-            entityStatePdu_2.setEntityType(createMalPacketType());
-            entityStatePdu_2.getEntityLocation().setX(7);
-            entityStatePdu_2.setForceId(ForceID.OPPOSING);
-
-            int MalPacketPingsReceived = 0;
-
-            FirePdu firePdu = pduFactory.makeFirePdu(); // for entity 1 first  weapon (possible traceroute)
-            // should we customize this munition?  what is it for your simulation?
-            EntityID fireID = new EntityID();
-            fireID.setSiteID(13);
-            fireID.setApplicationID(43);
-            fireID.setEntityID(103);
-            EntityID targetID = new EntityID();
-            targetID.setSiteID(66);
-            targetID.setApplicationID(666);
-            targetID.setEntityID(6666);
-
-            firePdu.setFiringEntityID(fireID);
-            firePdu.setTargetEntityID(targetID);
-
-            firePdu.setDescriptor(createTraceroute()); // calling create Traceroute Method
-
-            EntityID TracerouteID = new EntityID();
-            TracerouteID.setEntityID(1);
-            firePdu.setMunitionExpendibleID(TracerouteID);
-
-            CommentReliablePdu MalPacketDestroyedComment = pduFactory.makeCommentReliablePdu("Malware Packet DESTROYED BY Firewall");
-            CommentReliablePdu MalPacketDetectedComment = pduFactory.makeCommentReliablePdu("Firewall Detects Malware engage Traceroute");
-
-            // TODO simulation management PDUs for startup, planning to design special class support
-            //DetonationPdu detonationPdu = pduFactory.makeDetonationPdu();
-            //detonationPdu.setDescriptor(pDescriptor);
-            // loop the simulation while allowed, programmer can set additional conditions to break out and finish
-            while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT) // are we done yet?
-            {
-                simulationLoopCount++; // good practice: increment loop counter as first action in that loop
-
-                // =============================================================================================
-                // * your own simulation code starts here! *
-                // =============================================================================================
-                //  are there any other variables to modify at the beginning of your loop?
-                // compute a track, update an ESPDU, whatever it is that your model is doing...
-                // Where is my entity?  Insert changes in position; this sample only changes X position.
-                entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX()); // stationary defensive posture
-                entityStatePdu_2.getEntityLocation().setX(entityStatePdu_2.getEntityLocation().getX() - 1.0); // 1m per timestep
-
-                // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoot at!
-                Double range = entityStatePdu_2.getEntityLocation().getX();
-                System.out.println("range: " + range + " hops from our Network DMZ!");
-
-                if (range < 5) { // Range 5
-                    if (!fireBool) {
-                        sendSinglePdu(MalPacketDetectedComment);
-                    }
-                    fireBool = true;
-                    System.out.println("Entity#" + firePdu.getFiringEntityID().getEntityID() + " is firing " + firePdu.getDescriptor().getMunitionType().getDomain() + "." + firePdu.getDescriptor().getMunitionType().getCountry() + "." + firePdu.getDescriptor().getMunitionType().getCategory() + "." + firePdu.getDescriptor().getMunitionType().getSubCategory() + "." + firePdu.getDescriptor().getMunitionType().getSpecific() + "." + " at Entity#" + firePdu.getTargetEntityID().getEntityID());
-
-                    if (firePdu.getTargetEntityID().getEntityID() == 6666) {
-                        MalPacketPingsReceived += 1;
-                        if (MalPacketPingsReceived > 1) {
-                            // The Firewall destroys the MalPacket
-
-                            System.out.println("Malware Packet DESTROYED BY Firewall after " + MalPacketPingsReceived + "pings from the traceroute.");
-                            narrativeMessage4 = "Destroyed MalPacket";
-                            destBool = true;
-                            simulationComplete = true;
-
-                        }
-                    }
-                }
-                // etc. etc. your code goes here for your simulation of interest
-
-                // something happens between my simulation entities, la de da de da...
-                System.out.println("Phase complete. Next phase initiating...");
-
-                // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
-                narrativeMessage1 = "MV3500 Homework 3 - Simulation Program";
-                narrativeMessage2 = "runSimulation() loop " + simulationLoopCount;
-                narrativeMessage3 = "Simulation Started!"; // intentionally blank for testing
-
-                // your loop termination condition goes here
-                if (simulationLoopCount > 5) // for example
-                {
-                    simulationComplete = true;
-                }
-                // =============================================================================================
-                // * your own simulation code is finished here! *
-                // =============================================================================================
-
-                // staying synchronized with timestep: wait duration for elapsed time in this loop
-                // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
-                Thread.sleep((long) (SIMULATION_LOOP_DURATION_SECONDS * 1000)); // seconds * (1000 msec/sec) = milliseconds
-                System.out.println("... [Pausing for " + SIMULATION_LOOP_DURATION_SECONDS + " seconds]");
-
-                // OK now send the status PDUs for this loop, and then continue
-                System.out.println("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
-                sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu, timeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
-                sendSinglePdu(entityStatePdu_2); // me too i.e. 2!
-                System.out.println("... [PDUs successfully sent for this loop]");
-
-                // ===============================
-                // loop now finished, check whether to terminate if simulation complete, otherwise continue
-                if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
-                {
-                    //sendSinglePdu(detonationPdu);
-                    System.out.println("... [Termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + 
-                    break;
-                }
-            }   // end of simulation loop
-
-            narrativeMessage2 = "runSimulation() completed successfully"; // all done
-            sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
-            System.out.println("... [final CommentPdu successfully sent for simulation]");
-            // TODO simulation management PDUs
-        } catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
-        {
-            Logger.getLogger(ExampleSimulationProgram.class.getName()).log(Level.SEVERE, null, iex);
-        }
-    }
-    /* **************************** infrastructure code, modification is seldom needed ************************* */
-
-    String narrativeMessage1 = new String();
-    String narrativeMessage2 = new String();
-    String narrativeMessage3 = new String();
-    String narrativeMessage4 = new String();
-
-    /* VariableRecordType enumerations have potential use with CommentPdu logs */
- /* TODO contrast to EntityType */
-    VariableRecordType descriptionComment = VariableRecordType.DESCRIPTION;
-    VariableRecordType narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT;
-    VariableRecordType statusComment = VariableRecordType.APPLICATION_STATUS;
-    VariableRecordType timeStepComment = VariableRecordType.APPLICATION_TIMESTEP;
-    VariableRecordType otherComment = VariableRecordType.OTHER;
-
-    /**
-     * Output prefix to identify this class, helps with logging
-     */
-    private final static String TRACE_PREFIX = "[" + ExampleSimulationProgram.class.getName() + "] ";
-
-    // class variables
-    PduFactory pduFactory = new PduFactory();
-    DisThreadedNetworkInterface disNetworkInterface;
-    DisThreadedNetworkInterface.PduListener pduListener;
-    Pdu receivedPdu;
-    PduRecorder pduRecorder;
-
-    /**
-     * Constructor design goal: additional built-in initialization conveniences
-     * can go here to keep student efforts focused on the runSimulation()
-     * method.
-     */
-    public ExampleSimulationProgram() {
-        // Constructor is under consideration.  Constructor is not currently needed.
-    }
-
-    /**
-     * Utility Constructor that allows your example simulation program to
-     * override default network address and port
-     *
-     * @param address network address to use
-     * @param port corresponding network port to use
-     */
-    public ExampleSimulationProgram(String address, int port) {
-        setNetworkAddress(address);
-
-        setNetworkPort(port);
-    }
-
-    /**
-     * get networkAddress
-     *
-     * @return the networkAddress
-     */
-    public String getNetworkAddress() {
-        return networkAddress;
-    }
-
-    /**
-     * set networkAddress
-     *
-     * @param newNetworkAddress the networkAddress to set
-     */
-    public final void setNetworkAddress(String newNetworkAddress) {
-        ExampleSimulationProgram.networkAddress = newNetworkAddress;
-    }
-
-    /**
-     * get networkPort
-     *
-     * @return the networkPort
-     */
-    public int getNetworkPort() {
-        return networkPort;
-    }
-
-    /**
-     * set networkPort
-     *
-     * @param newNetworkPort the networkPort to set
-     */
-    public final void setNetworkPort(int newNetworkPort) {
-        ExampleSimulationProgram.networkPort = newNetworkPort;
-    }
-
-    /**
-     * Initialize network interface, choosing best available network interface
-     */
-    public void setUpNetworkInterface() {
-        disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort());
-        disNetworkInterface.setDescriptor("ExampleSimulationProgramAllen_3 pdu looping");
-
-        System.out.println("Network confirmation:"
-                + " address=" + disNetworkInterface.getAddress()
-                + //  disNetworkInterface.getMulticastGroup() + 
-                " port=" + disNetworkInterface.getPort());   // + disNetworkInterface.getDisPort());
-        pduListener = new DisThreadedNetworkInterface.PduListener() {
-            /**
-             * Callback handler for listener
-             */
-            @Override
-            public void incomingPdu(Pdu newPdu) {
-                receivedPdu = newPdu;
-            }
-        };
-        disNetworkInterface.addListener(pduListener);
-
-        String outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
-        System.out.println("Beginning pdu save to directory " + outputDirectory);
-        pduRecorder = new PduRecorder(outputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save
-        pduRecorder.setDescriptor("ExampleSimulationProgramAllen_3 pduRecorder");
-        pduRecorder.start(); // begin running
-    }
-
-    /**
-     * All done, release network resources
-     */
-    public void tearDownNetworkInterface() {
-        pduRecorder.stop();
-
-        disNetworkInterface.removeListener(pduListener);
-
-        disNetworkInterface.close();
-//      disNetworkInterface.kill(); // renamed as close(), deprecated
-//      disNetworkInterface = null; // making sure no possibility of zombie process remaining...
-    }
-
-    /**
-     * Send a single Protocol Data Unit (PDU) of any type
-     *
-     * @param pdu the pdu to send
-     */
-    private void sendSinglePdu(Pdu pdu) {
-        try {
-            disNetworkInterface.send(pdu);
-            Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
-        } catch (InterruptedException ex) {
-            System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
-            System.exit(1);
-        }
-    }
-
-    /**
-     * Send Comment PDU
-     *
-     * @see
-     * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing
-     * Information to a Method or a Constructor</a> Arbitrary Number of
-     * Arguments
-     * @param commentType enumeration value describing purpose of the narrative
-     * comment
-     * @param comments String array of narrative comments
-     */
-    public void sendCommentPdu(VariableRecordType commentType,
-            // vararg... variable-length set of String comments can optionally follow
-            String... comments) {
-        sendAllPdusForLoopTimestep(null, null, commentType, comments);
-    }
-
-    /**
-     * Send EntityState, Fire, Comment PDUs that got updated for this loop,
-     * reflecting state of current simulation timestep.
-     *
-     * @see
-     * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing
-     * Information to a Method or a Constructor</a> Arbitrary Number of
-     * Arguments
-     * @param entityStatePdu the ESPDU to send, if any
-     * @param firePdu the FirePDU to send, if any
-     * @param commentType enumeration value describing purpose of the narrative
-     * comment
-     * @param comments String array of narrative comments
-     */
-    public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu,
-            FirePdu firePdu,
-            VariableRecordType commentType,
-            // vararg... variable-length set of String comments can optionally follow
-            String... comments) {
-        if (entityStatePdu != null) {
-            sendSinglePdu(entityStatePdu);
-        }
-
-        if (firePdu != null) {
-            sendSinglePdu(firePdu); // bang
-        }
-        if ((comments != null) && (comments.length > 0)) {
-            ArrayList<String> newCommentsList = new ArrayList<>();
-            for (String comment : comments) {
-                if (!comment.isEmpty()) {
-                    newCommentsList.add(comment); // OK found something to send
-                }
-            }
-            if (!newCommentsList.isEmpty()) {
-                if (commentType == null) {
-                    commentType = otherComment; // fallback value otherComment
-                }                // now build the commentPdu from these string inputs, thus constructing a narrative entry
-                CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments);
-                sendSinglePdu(commentPdu);
-                if (isVerboseComments()) {
-                    System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString());
-                }
-            }
-        }
-    }
-
-    /**
-     * Main method is first executed when a program instance is loaded.
-     *
-     * @see
-     * <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java
-     * Tutorials: A Closer Look at the "Hello World!" Application</a>
-     * @param args command-line arguments are an array of optional String
-     * parameters that are passed from execution environment during invocation
-     */
-    public static void main(String[] args) {
-        System.out.println(TRACE_PREFIX + "started...");
-
-        ExampleSimulationProgram thisProgram = new ExampleSimulationProgram(); // creates instance
-
-        // initial execution: can handle args array of initialization arguments here
-        if (args.length == 2) {
-            if ((args[0] != null) && !args[0].isEmpty()) {
-                thisProgram.setNetworkAddress(args[0]);
-            }
-
-            if ((args[1] != null) && !args[1].isEmpty()) {
-                thisProgram.setNetworkPort(Integer.parseInt(args[1]));
-            }
-        } else if (args.length != 0) {
-            System.err.println("Usage: " + thisProgram.getClass().getName() + " [address port]");
-            System.exit(-1);
-        }
-        // OK here we go...
-
-        thisProgram.setUpNetworkInterface();
-
-        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
-
-        thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering
-
-        System.out.println(TRACE_PREFIX + "complete."); // report successful completion
-    }
-
-    /**
-     * get whether verbose comments are enabled
-     *
-     * @return whether verboseComments mode is enabled
-     */
-    public boolean isVerboseComments() {
-        return verboseComments;
-    }
-
-    /**
-     * set whether verbose comments are enabled
-     *
-     * @param newVerboseComments whether verboseComments mode is enabled
-     */
-    public void setVerboseComments(boolean newVerboseComments) {
-        this.verboseComments = newVerboseComments;
-    }
-}
+/**
+ * Copyright (c) 2008-2021, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved.
+ * This work is provided under a BSD open-source license, see project license.html and license.txt
+ *
+ * This Program is a modified version of ExampleSimulationProgram in order to see the
+ * verbose plain text pdu log. This simulates a firewall identifying a malicious packet
+ * and tracing its source before destroying it.
+ * 
+ * and tracing its source before destroying it - updated September 12, 2021 to ensure
+ * correct file pushed to Gitlab.
+ *
+ * @author Bruce Chojnacki
+ */
+package MV3500Cohort2023MarchJune.homework3.Chojnacki;
+
+import edu.nps.moves.dis7.enumerations.*; // match any
+import edu.nps.moves.dis7.pdus.*;         // match any of the PDU classes, easier than listing individually
+import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface;
+import edu.nps.moves.dis7.utilities.PduFactory;
+import edu.nps.moves.dis7.utilities.stream.PduRecorder;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The purpose of this program is to provide an easily modifiable example
+ * simulation program that includes DIS-capable entities doing tasks and
+ * reporting them to the network. Default settings include PDU recording turned
+ * on by default.
+ */
+public class ExampleSimulationProgram {
+
+    private boolean verboseComments = true;
+    static final String NETWORK_ADDRESS_DEFAULT = "239.1.2.3";
+    static final int NETWORK_PORT_DEFAULT = 3000;
+    static String networkAddress = NETWORK_ADDRESS_DEFAULT;
+    static int networkPort = NETWORK_PORT_DEFAULT;
+    String DEFAULT_OUTPUT_DIRECTORY = "./pduLog";
+
+    private EntityID createFriendFireWall() {
+        EntityID FNFireWallID = new EntityID(); // 1.1.225.1.1.1 Platform,Cyber,USA,FireWall
+        FNFireWallID.setSiteID(13);
+        FNFireWallID.setApplicationID(43);
+        FNFireWallID.setEntityID(103);
+        return FNFireWallID;
+    }
+
+    private EntityType createFriendFireWallType() {
+        EntityType FNFireWallType = new EntityType();
+        FNFireWallType.setEntityKind(EntityKind.PLATFORM);
+        FNFireWallType.setDomain(Domain.inst(PlatformDomain.OTHER));
+        FNFireWallType.setCountry(Country.UNITED_STATES_OF_AMERICA_USA);
+        FNFireWallType.setCategory(3);
+        FNFireWallType.setSubCategory(1);
+        FNFireWallType.setSpecific(1);
+        return FNFireWallType;
+    }
+
+    private EntityID createMalPacket() {
+        EntityID MalPacketID = new EntityID(); // 1.1.45.1.7.1 Platform,Cyber,China,MaliciousPacket
+        MalPacketID.setSiteID(66);
+        MalPacketID.setApplicationID(666);
+        MalPacketID.setEntityID(6666);
+        return MalPacketID;
+    }
+
+    private EntityType createMalPacketType() {
+        EntityType MalPacketType = new EntityType();
+        MalPacketType.setEntityKind(EntityKind.PLATFORM);
+        MalPacketType.setDomain(Domain.inst(PlatformDomain.OTHER));
+        MalPacketType.setCountry(Country.CHINA_PEOPLES_REPUBLIC_OF_CHN);
+        MalPacketType.setCategory(3);
+        MalPacketType.setSubCategory(1);
+        MalPacketType.setSpecific(1);
+        return MalPacketType;
+    }
+
+    private MunitionDescriptor createTraceroute() {
+
+        EntityType TracerouteType = new EntityType(); //2.2.225.2.13.1
+        TracerouteType.setEntityKind(EntityKind.MUNITION);
+        TracerouteType.setDomain(Domain.inst(PlatformDomain.OTHER));
+        TracerouteType.setCountry(Country.UNITED_STATES_OF_AMERICA_USA);
+        TracerouteType.setCategory(2);
+        TracerouteType.setSubCategory(8);
+        TracerouteType.setSpecific(1);
+        MunitionDescriptor Traceroute = new MunitionDescriptor();
+        Traceroute.setMunitionType(TracerouteType);
+        Traceroute.setQuantity(1000);
+        Traceroute.setFuse(MunitionDescriptorFuse.CONTACT);
+        Traceroute.setRate(200);
+        return Traceroute;
+    }
+
+    /**
+     * This runSimulationLoops() method is for you, a programmer-modifiable
+     * method for defining and running a new simulation of interest. Welcome!
+     * Other parts of this program handle bookkeeping and plumbing tasks so that
+     * you can focus on your model entities and activities. Expandable support
+     * includes DIS EntityStatePdu, FirePdu and CommentPdu all available for
+     * modification and sending in a simulation loop. Continuous improvement
+     * efforts seek to make this program as easy and straightforward as possible
+     * for DIS simulationists to use and adapt. All of the other methods are
+     * setup, teardown and configuration that you may find interesting, even
+     * helpful, but don't really have to worry about.
+     */
+    @SuppressWarnings("SleepWhileInLoop") // yes we do that
+    public void runSimulationLoops() {
+        try {
+            /**
+             * seconds for real-time execution (not simulation time, which may
+             * or may not be the same)
+             */
+            final double SIMULATION_LOOP_DURATION_SECONDS = 1.0;
+            final int SIMULATION_MAX_LOOP_COUNT = 15; // be deliberate out out there!  also avoid infinite loops.
+            int simulationLoopCount = 0;        // variable, initialized at 0
+            boolean simulationComplete = false;     // sentinel variable as termination condition,, are we done yet?
+            boolean fireBool = false;
+            boolean destBool = false;
+
+            // TODO reset clock to zero each time for consistent outputs
+            // Your model setup: define participants.  who's who in this zoo?
+            // Assuming you keep track of entity objects...  here is some support for for Entity 1.
+            // create PDU object for US Firewall and set its values.
+            //EntityID       entityID_1    = new EntityID();
+            //entityID_1.setSiteID(1).setApplicationID(2).setEntityID(3); // made-up example ID; 
+            EntityStatePdu entityStatePdu_1 = pduFactory.makeEntityStatePdu();
+            entityStatePdu_1.setEntityID(createFriendFireWall());
+            entityStatePdu_1.setEntityType(createFriendFireWallType());
+            entityStatePdu_1.getEntityLocation().setX(0);
+            entityStatePdu_1.setForceId(ForceID.FRIENDLY);
+
+            // TODO someday, use enumerations; is there a unique site triplet for MOVES Institute?
+            // create PDU object for Malware Packet and set its values.
+            //EntityID       entityID_2    = new EntityID();
+            //entityID_2.setSiteID(4).setApplicationID(5).setEntityID(6);
+            EntityStatePdu entityStatePdu_2 = pduFactory.makeEntityStatePdu();
+            entityStatePdu_2.setEntityID(createMalPacket());
+            entityStatePdu_2.setEntityType(createMalPacketType());
+            entityStatePdu_2.getEntityLocation().setX(7);
+            entityStatePdu_2.setForceId(ForceID.OPPOSING);
+
+            int MalPacketPingsReceived = 0;
+
+            FirePdu firePdu = pduFactory.makeFirePdu(); // for entity 1 first  weapon (possible traceroute)
+            // should we customize this munition?  what is it for your simulation?
+            EntityID fireID = new EntityID();
+            fireID.setSiteID(13);
+            fireID.setApplicationID(43);
+            fireID.setEntityID(103);
+            EntityID targetID = new EntityID();
+            targetID.setSiteID(66);
+            targetID.setApplicationID(666);
+            targetID.setEntityID(6666);
+
+            firePdu.setFiringEntityID(fireID);
+            firePdu.setTargetEntityID(targetID);
+
+            firePdu.setDescriptor(createTraceroute()); // calling create Traceroute Method
+
+            EntityID TracerouteID = new EntityID();
+            TracerouteID.setEntityID(1);
+            firePdu.setMunitionExpendibleID(TracerouteID);
+
+            CommentReliablePdu MalPacketDestroyedComment = pduFactory.makeCommentReliablePdu("Malware Packet DESTROYED BY Firewall");
+            CommentReliablePdu MalPacketDetectedComment = pduFactory.makeCommentReliablePdu("Firewall Detects Malware engage Traceroute");
+
+            // TODO simulation management PDUs for startup, planning to design special class support
+            //DetonationPdu detonationPdu = pduFactory.makeDetonationPdu();
+            //detonationPdu.setDescriptor(pDescriptor);
+            // loop the simulation while allowed, programmer can set additional conditions to break out and finish
+            while (simulationLoopCount < SIMULATION_MAX_LOOP_COUNT) // are we done yet?
+            {
+                simulationLoopCount++; // good practice: increment loop counter as first action in that loop
+
+                // =============================================================================================
+                // * your own simulation code starts here! *
+                // =============================================================================================
+                //  are there any other variables to modify at the beginning of your loop?
+                // compute a track, update an ESPDU, whatever it is that your model is doing...
+                // Where is my entity?  Insert changes in position; this sample only changes X position.
+                entityStatePdu_1.getEntityLocation().setX(entityStatePdu_1.getEntityLocation().getX()); // stationary defensive posture
+                entityStatePdu_2.getEntityLocation().setX(entityStatePdu_2.getEntityLocation().getX() - 1.0); // 1m per timestep
+
+                // decide whether to fire, and then update the firePdu.  Hmmm, you might want a target to shoot at!
+                Double range = entityStatePdu_2.getEntityLocation().getX();
+                System.out.println("range: " + range + " hops from our Network DMZ!");
+
+                if (range < 5) { // Range 5
+                    if (!fireBool) {
+                        sendSinglePdu(MalPacketDetectedComment);
+                    }
+                    fireBool = true;
+                    System.out.println("Entity#" + firePdu.getFiringEntityID().getEntityID() + " is firing " + firePdu.getDescriptor().getMunitionType().getDomain() + "." + firePdu.getDescriptor().getMunitionType().getCountry() + "." + firePdu.getDescriptor().getMunitionType().getCategory() + "." + firePdu.getDescriptor().getMunitionType().getSubCategory() + "." + firePdu.getDescriptor().getMunitionType().getSpecific() + "." + " at Entity#" + firePdu.getTargetEntityID().getEntityID());
+
+                    if (firePdu.getTargetEntityID().getEntityID() == 6666) {
+                        MalPacketPingsReceived += 1;
+                        if (MalPacketPingsReceived > 1) {
+                            // The Firewall destroys the MalPacket
+
+                            System.out.println("Malware Packet DESTROYED BY Firewall after " + MalPacketPingsReceived + "pings from the traceroute.");
+                            narrativeMessage4 = "Destroyed MalPacket";
+                            destBool = true;
+                            simulationComplete = true;
+
+                        }
+                    }
+                }
+                // etc. etc. your code goes here for your simulation of interest
+
+                // something happens between my simulation entities, la de da de da...
+                System.out.println("Phase complete. Next phase initiating...");
+
+                // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
+                narrativeMessage1 = "MV3500 Homework 3 - Simulation Program";
+                narrativeMessage2 = "runSimulation() loop " + simulationLoopCount;
+                narrativeMessage3 = "Simulation Started!"; // intentionally blank for testing
+
+                // your loop termination condition goes here
+                if (simulationLoopCount > 5) // for example
+                {
+                    simulationComplete = true;
+                }
+                // =============================================================================================
+                // * your own simulation code is finished here! *
+                // =============================================================================================
+
+                // staying synchronized with timestep: wait duration for elapsed time in this loop
+                // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes
+                Thread.sleep((long) (SIMULATION_LOOP_DURATION_SECONDS * 1000)); // seconds * (1000 msec/sec) = milliseconds
+                System.out.println("... [Pausing for " + SIMULATION_LOOP_DURATION_SECONDS + " seconds]");
+
+                // OK now send the status PDUs for this loop, and then continue
+                System.out.println("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
+                sendAllPdusForLoopTimestep(entityStatePdu_1, firePdu, timeStepComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
+                sendSinglePdu(entityStatePdu_2); // me too i.e. 2!
+                System.out.println("... [PDUs successfully sent for this loop]");
+
+                // ===============================
+                // loop now finished, check whether to terminate if simulation complete, otherwise continue
+                if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good
+                {
+                    //sendSinglePdu(detonationPdu);
+                    System.out.println("... [Termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + 
+                    break;
+                }
+            }   // end of simulation loop
+
+            narrativeMessage2 = "runSimulation() completed successfully"; // all done
+            sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
+            System.out.println("... [final CommentPdu successfully sent for simulation]");
+            // TODO simulation management PDUs
+        } catch (InterruptedException iex) // handle any exception that your code might choose to provoke!
+        {
+            Logger.getLogger(ExampleSimulationProgram.class.getName()).log(Level.SEVERE, null, iex);
+        }
+    }
+    /* **************************** infrastructure code, modification is seldom needed ************************* */
+
+    String narrativeMessage1 = new String();
+    String narrativeMessage2 = new String();
+    String narrativeMessage3 = new String();
+    String narrativeMessage4 = new String();
+
+    /* VariableRecordType enumerations have potential use with CommentPdu logs */
+ /* TODO contrast to EntityType */
+    VariableRecordType descriptionComment = VariableRecordType.DESCRIPTION;
+    VariableRecordType narrativeComment = VariableRecordType.COMPLETE_EVENT_REPORT;
+    VariableRecordType statusComment = VariableRecordType.APPLICATION_STATUS;
+    VariableRecordType timeStepComment = VariableRecordType.APPLICATION_TIMESTEP;
+    VariableRecordType otherComment = VariableRecordType.OTHER;
+
+    /**
+     * Output prefix to identify this class, helps with logging
+     */
+    private final static String TRACE_PREFIX = "[" + ExampleSimulationProgram.class.getName() + "] ";
+
+    // class variables
+    PduFactory pduFactory = new PduFactory();
+    DisThreadedNetworkInterface disNetworkInterface;
+    DisThreadedNetworkInterface.PduListener pduListener;
+    Pdu receivedPdu;
+    PduRecorder pduRecorder;
+
+    /**
+     * Constructor design goal: additional built-in initialization conveniences
+     * can go here to keep student efforts focused on the runSimulation()
+     * method.
+     */
+    public ExampleSimulationProgram() {
+        // Constructor is under consideration.  Constructor is not currently needed.
+    }
+
+    /**
+     * Utility Constructor that allows your example simulation program to
+     * override default network address and port
+     *
+     * @param address network address to use
+     * @param port corresponding network port to use
+     */
+    public ExampleSimulationProgram(String address, int port) {
+        setNetworkAddress(address);
+
+        setNetworkPort(port);
+    }
+
+    /**
+     * get networkAddress
+     *
+     * @return the networkAddress
+     */
+    public String getNetworkAddress() {
+        return networkAddress;
+    }
+
+    /**
+     * set networkAddress
+     *
+     * @param newNetworkAddress the networkAddress to set
+     */
+    public final void setNetworkAddress(String newNetworkAddress) {
+        ExampleSimulationProgram.networkAddress = newNetworkAddress;
+    }
+
+    /**
+     * get networkPort
+     *
+     * @return the networkPort
+     */
+    public int getNetworkPort() {
+        return networkPort;
+    }
+
+    /**
+     * set networkPort
+     *
+     * @param newNetworkPort the networkPort to set
+     */
+    public final void setNetworkPort(int newNetworkPort) {
+        ExampleSimulationProgram.networkPort = newNetworkPort;
+    }
+
+    /**
+     * Initialize network interface, choosing best available network interface
+     */
+    public void setUpNetworkInterface() {
+        disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort());
+        disNetworkInterface.setDescriptor("ExampleSimulationProgramAllen_3 pdu looping");
+
+        System.out.println("Network confirmation:"
+                + " address=" + disNetworkInterface.getAddress()
+                + //  disNetworkInterface.getMulticastGroup() + 
+                " port=" + disNetworkInterface.getPort());   // + disNetworkInterface.getDisPort());
+        pduListener = new DisThreadedNetworkInterface.PduListener() {
+            /**
+             * Callback handler for listener
+             */
+            @Override
+            public void incomingPdu(Pdu newPdu) {
+                receivedPdu = newPdu;
+            }
+        };
+        disNetworkInterface.addListener(pduListener);
+
+        String outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
+        System.out.println("Beginning pdu save to directory " + outputDirectory);
+        pduRecorder = new PduRecorder(outputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save
+        pduRecorder.setDescriptor("ExampleSimulationProgramAllen_3 pduRecorder");
+        pduRecorder.start(); // begin running
+    }
+
+    /**
+     * All done, release network resources
+     */
+    public void tearDownNetworkInterface() {
+        pduRecorder.stop();
+
+        disNetworkInterface.removeListener(pduListener);
+
+        disNetworkInterface.close();
+//      disNetworkInterface.kill(); // renamed as close(), deprecated
+//      disNetworkInterface = null; // making sure no possibility of zombie process remaining...
+    }
+
+    /**
+     * Send a single Protocol Data Unit (PDU) of any type
+     *
+     * @param pdu the pdu to send
+     */
+    private void sendSinglePdu(Pdu pdu) {
+        try {
+            disNetworkInterface.sendPDU(pdu);
+            Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
+        } catch (InterruptedException ex) {
+            System.err.println(this.getClass().getName() + " Error sending PDU: " + ex.getLocalizedMessage());
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Send Comment PDU
+     *
+     * @see
+     * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing
+     * Information to a Method or a Constructor</a> Arbitrary Number of
+     * Arguments
+     * @param commentType enumeration value describing purpose of the narrative
+     * comment
+     * @param comments String array of narrative comments
+     */
+    public void sendCommentPdu(VariableRecordType commentType,
+            // vararg... variable-length set of String comments can optionally follow
+            String... comments) {
+        sendAllPdusForLoopTimestep(null, null, commentType, comments);
+    }
+
+    /**
+     * Send EntityState, Fire, Comment PDUs that got updated for this loop,
+     * reflecting state of current simulation timestep.
+     *
+     * @see
+     * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing
+     * Information to a Method or a Constructor</a> Arbitrary Number of
+     * Arguments
+     * @param entityStatePdu the ESPDU to send, if any
+     * @param firePdu the FirePDU to send, if any
+     * @param commentType enumeration value describing purpose of the narrative
+     * comment
+     * @param comments String array of narrative comments
+     */
+    public void sendAllPdusForLoopTimestep(EntityStatePdu entityStatePdu,
+            FirePdu firePdu,
+            VariableRecordType commentType,
+            // vararg... variable-length set of String comments can optionally follow
+            String... comments) {
+        if (entityStatePdu != null) {
+            sendSinglePdu(entityStatePdu);
+        }
+
+        if (firePdu != null) {
+            sendSinglePdu(firePdu); // bang
+        }
+        if ((comments != null) && (comments.length > 0)) {
+            ArrayList<String> newCommentsList = new ArrayList<>();
+            for (String comment : comments) {
+                if (!comment.isEmpty()) {
+                    newCommentsList.add(comment); // OK found something to send
+                }
+            }
+            if (!newCommentsList.isEmpty()) {
+                if (commentType == null) {
+                    commentType = otherComment; // fallback value otherComment
+                }                // now build the commentPdu from these string inputs, thus constructing a narrative entry
+                CommentPdu commentPdu = pduFactory.makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments);
+                sendSinglePdu(commentPdu);
+                if (isVerboseComments()) {
+                    System.out.println("*** [Narrative comment sent: " + commentType.name() + "] " + newCommentsList.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Main method is first executed when a program instance is loaded.
+     *
+     * @see
+     * <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java
+     * Tutorials: A Closer Look at the "Hello World!" Application</a>
+     * @param args command-line arguments are an array of optional String
+     * parameters that are passed from execution environment during invocation
+     */
+    public static void main(String[] args) {
+        System.out.println(TRACE_PREFIX + "started...");
+
+        ExampleSimulationProgram thisProgram = new ExampleSimulationProgram(); // creates instance
+
+        // initial execution: can handle args array of initialization arguments here
+        if (args.length == 2) {
+            if ((args[0] != null) && !args[0].isEmpty()) {
+                thisProgram.setNetworkAddress(args[0]);
+            }
+
+            if ((args[1] != null) && !args[1].isEmpty()) {
+                thisProgram.setNetworkPort(Integer.parseInt(args[1]));
+            }
+        } else if (args.length != 0) {
+            System.err.println("Usage: " + thisProgram.getClass().getName() + " [address port]");
+            System.exit(-1);
+        }
+        // OK here we go...
+
+        thisProgram.setUpNetworkInterface();
+
+        thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ...
+
+        thisProgram.tearDownNetworkInterface(); // make sure no processes are left lingering
+
+        System.out.println(TRACE_PREFIX + "complete."); // report successful completion
+    }
+
+    /**
+     * get whether verbose comments are enabled
+     *
+     * @return whether verboseComments mode is enabled
+     */
+    public boolean isVerboseComments() {
+        return verboseComments;
+    }
+
+    /**
+     * set whether verbose comments are enabled
+     *
+     * @param newVerboseComments whether verboseComments mode is enabled
+     */
+    public void setVerboseComments(boolean newVerboseComments) {
+        this.verboseComments = newVerboseComments;
+    }
+}
diff --git a/assignments/src/MV3500Cohort2023MarchJune/homework3/Oblak/ExampleSimulationProgram.java b/assignments/src/MV3500Cohort2023MarchJune/homework3/Oblak/ExampleSimulationProgram.java
index aec56e3dea2ca7e4db26950968c97b73e45313d0..deb0601ecd5c051c21821bdaa7361f3d789ca795 100644
--- a/assignments/src/MV3500Cohort2023MarchJune/homework3/Oblak/ExampleSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2023MarchJune/homework3/Oblak/ExampleSimulationProgram.java
@@ -407,7 +407,7 @@ public class ExampleSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2023MarchJune/homework3/Sloan/SloanExampleSimulationProgram.java b/assignments/src/MV3500Cohort2023MarchJune/homework3/Sloan/SloanExampleSimulationProgram.java
index 62f857ba116ee9654a699234a8f0b7ae53f606de..06baaecd23744707c8c298b24cd45ec5e47de5cb 100644
--- a/assignments/src/MV3500Cohort2023MarchJune/homework3/Sloan/SloanExampleSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2023MarchJune/homework3/Sloan/SloanExampleSimulationProgram.java
@@ -291,7 +291,7 @@ public class SloanExampleSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)
diff --git a/assignments/src/MV3500Cohort2023MarchJune/homework3/Tidwell/ExampleSimulationProgram.java b/assignments/src/MV3500Cohort2023MarchJune/homework3/Tidwell/ExampleSimulationProgram.java
index b9854bc3f6720420fa02eb6876045b9f746670df..8ce33fd4e806c887fd6887b77c86647c0dbaea2c 100644
--- a/assignments/src/MV3500Cohort2023MarchJune/homework3/Tidwell/ExampleSimulationProgram.java
+++ b/assignments/src/MV3500Cohort2023MarchJune/homework3/Tidwell/ExampleSimulationProgram.java
@@ -395,7 +395,7 @@ public class ExampleSimulationProgram
     {
         try
         {
-            disNetworkInterface.send(pdu);
+            disNetworkInterface.sendPDU(pdu);
             Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally
         } 
         catch (InterruptedException ex)