diff --git a/examples/src/OpenDis7Examples/ExampleTrackInterpolation.java b/examples/src/OpenDis7Examples/ExampleTrackInterpolation.java
index a4633ec3f1e5b5537cb36444f3721562b86f76c8..d3bf1309811f6f366142355b2b42202d1413a448 100644
--- a/examples/src/OpenDis7Examples/ExampleTrackInterpolation.java
+++ b/examples/src/OpenDis7Examples/ExampleTrackInterpolation.java
@@ -82,6 +82,11 @@ public class ExampleTrackInterpolation extends ExampleSimulationProgram
             float speedEntity_1 = 1.0f; // meters/second
             EntityStatePdu.Direction directionEntity_1 = EntityStatePdu.Direction.NORTH;
             
+            PduTrack pduTrack_1 = new PduTrack();
+            pduTrack_1.setDescriptor("testing 123");
+            pduTrack_1.setWaypointInterval(1.0f); // overrides timestamps
+            pduTrack_1.addPdu(espdu_1); // initial location
+            
             // OK send initial PDUs prior to loop
             if (pduRecorder.hasVerboseOutput())
                 System.out.println("sending PDUs for simulation step " + simulationLoopCount + ", monitor loopback to confirm sent");
@@ -118,8 +123,6 @@ public class ExampleTrackInterpolation extends ExampleSimulationProgram
                 
                 // Where is my entity?  Insert changes in position; this sample only changes X position.
                 espdu_1.advanceEntityLocation(currentTimeStep);
-                
-                Vector3Double location = espdu_1.getEntityLocation();
 
                 // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending)
                 narrativeMessage1 = "MV3500 TrackSimulationProgram";
@@ -151,6 +154,8 @@ public class ExampleTrackInterpolation extends ExampleSimulationProgram
                 if (pduRecorder.hasVerboseOutput())
                     System.out.println(TRACE_PREFIX + "PDUs successfully sent for this loop");
                 pduList.add(espdu_1);
+                pduTrack_1.addPdu(espdu_1);
+                Vector3Double location = espdu_1.getEntityLocation();
                 reportPdu(simulationLoopCount, location, directionEntity_1);
                 
                 // ===============================
@@ -163,6 +168,14 @@ public class ExampleTrackInterpolation extends ExampleSimulationProgram
             }   // end of simulation loop
             System.out.println(TRACE_PREFIX + "all PDUs successfully sent for this loop, pduList.size()=" + pduList.size());
                 
+            pduTrack_1 = pduTrack_1.sortPdus().createRawWaypoints();
+            
+            System.out.println("pduTrack_1 getEspduCount()=" + pduTrack_1.getEspduCount());
+            System.out.println("pduTrack_1 duration = " + pduTrack_1.getTrackDurationSeconds() + " seconds = " +
+                                                          pduTrack_1.getTrackDurationTicks() + " ticks");
+            System.out.println(pduTrack_1.createX3dTimeSensorString());
+            System.out.println(pduTrack_1.createX3dPositionInterpolatorString());
+            
             narrativeMessage2 = "runSimulation() completed successfully"; // all done
             sendCommentPdu(narrativeComment, narrativeMessage1, narrativeMessage2, narrativeMessage3);
             if (pduRecorder.hasVerboseOutput())
diff --git a/examples/src/OpenDis7Examples/ExampleTrackInterpolationLog.txt b/examples/src/OpenDis7Examples/ExampleTrackInterpolationLog.txt
index f045873beef8fc7fe6e72185691ea0120e90c5c4..e0ed9f91c65c7be2f8ffe57db4e2ee4f1b73ee4e 100644
--- a/examples/src/OpenDis7Examples/ExampleTrackInterpolationLog.txt
+++ b/examples/src/OpenDis7Examples/ExampleTrackInterpolationLog.txt
@@ -9,15 +9,15 @@ run-single:
 [OpenDis7Examples.ExampleTrackInterpolation] main() started...
 [DisThreadedNetworkInterface] using network interface Intel(R) Wi-Fi 6E AX210 160MHz
 [DisThreadedNetworkInterface] datagramSocket.joinGroup  address=239.1.2.3 port=3000 isConnected()=false createDatagramSocket() complete.
-DisThreadedNetworkInterface createThreads() receiveThread.isAlive()=true
-DisThreadedNetworkInterface createThreads() sendingThread.isAlive()=true
+[DisThreadedNetworkInterface] createThreads() receiveThread.isAlive()=true
+[DisThreadedNetworkInterface] createThreads() sendingThread.isAlive()=true
 Network confirmation: address=239.1.2.3 port=3000
 Beginning pdu save to directory ./pduLog
-Recorder log file open: C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\pduLog\PduCaptureLog112.dislog
+Recorder log file open: C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\pduLog\PduCaptureLog132.dislog
 [DisThreadedNetworkInterface] using network interface Intel(R) Wi-Fi 6E AX210 160MHz
 [DisThreadedNetworkInterface] datagramSocket.joinGroup  address=239.1.2.3 port=3000 isConnected()=false createDatagramSocket() complete.
-DisThreadedNetworkInterface createThreads() receiveThread.isAlive()=true
-DisThreadedNetworkInterface createThreads() sendingThread.isAlive()=true
+[DisThreadedNetworkInterface] createThreads() receiveThread.isAlive()=true
+[DisThreadedNetworkInterface] createThreads() sendingThread.isAlive()=true
 [PduRecorder PduRecorder] listening to IP address 239.1.2.3 on port 3000
  0 Entity location=( 0.0,  0.0,  0.0) NORTH
  1 Entity location=( 0.0,  1.0,  0.0) NORTH
@@ -62,12 +62,16 @@ DisThreadedNetworkInterface createThreads() sendingThread.isAlive()=true
 40 Entity location=( 0.0,  0.0,  0.0) WEST 
 [OpenDis7Examples.ExampleTrackInterpolation] loop termination condition met, simulationComplete=true
 [OpenDis7Examples.ExampleTrackInterpolation] all PDUs successfully sent for this loop, pduList.size()=41
-*** setKillSentinelAndInterrupts() killed=true sendingThread.isInterrupted()=true receiveThread.isInterrupted()=true
-*** DisThreadedNetworkInterface close(): pdus2send.size()=0 baos.size()=0 dos.size()=0
+pduTrack_1 getEspduCount()=41
+pduTrack_1 duration = 41.0 seconds = -1 ticks
+<TimeSensor DEF='testing123Clock' cycleInterval='41.0' loop='true'/>
+<PositionInterpolator DEF='testing123Positions' key='0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0 31.0 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0 40.0' keyValue='0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0,0.0 0.0 0.0'/>
+*** setKillSentinelAndInterrupts() killed=true sendingThread.isInterrupted()=false receiveThread.isInterrupted()=true
+[DisThreadedNetworkInterface PduRecorder] close(): pdus2send.size()=0 baos.size()=0 dos.size()=0
 *** killThread() status: sendingThread.isAlive()=false sendingThread.isInterrupted()=true
 *** killThread() status: receiveThread.isAlive()=false receiveThread.isInterrupted()=true
 *** Thread close status: sendingThread.isAlive()=false receiveThread.isAlive()=false
 
-Closing recorder log file: C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\pduLog\PduCaptureLog112.dislog
+PduRecorder.selfTest() stop() closing recorder log file: C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\pduLog\PduCaptureLog132.dislog
 [OpenDis7Examples.ExampleTrackInterpolation] complete.
-BUILD SUCCESSFUL (total time: 12 seconds)
+BUILD SUCCESSFUL (total time: 11 seconds)
diff --git a/examples/src/OpenDis7Examples/PduTrack.java b/examples/src/OpenDis7Examples/PduTrack.java
index f96abb1aff0528b47ae9187183eb8c349caacb79..75bce6a52ac0f38714bfdb50112ef5ef04cc699d 100644
--- a/examples/src/OpenDis7Examples/PduTrack.java
+++ b/examples/src/OpenDis7Examples/PduTrack.java
@@ -46,15 +46,18 @@ import java.util.ArrayList;
  * Create a track from DIS ESPDUs
  * @author brutzman
  */
-public class PduTrack {
-    
+public class PduTrack
+{   
+    private String      descriptor = new String();
     private ArrayList<Pdu> pduList = new ArrayList<>();
     
     private ArrayList<Vector3Double> waypointsList = new ArrayList<>();
     private ArrayList<EulerAngles>      anglesList = new ArrayList<>();
-    private ArrayList<Integer>       timestampList = new ArrayList<>();
+    /** timelineList in seconds */
+    private ArrayList<Float>       timelineList = new ArrayList<>();
     
-    private String      descriptor = new String();
+    private float             defaultWaypointInterval = -1;
+    private float                     durationSeconds = -1;
     private String                   x3dTimeSensorDEF = new String();
     private String         x3dPositionInterpolatorDEF = new String();
     private String      x3dOrientationInterpolatorDEF = new String();
@@ -102,10 +105,11 @@ public class PduTrack {
         return anglesList;
     }
     /**
-     * @return the timestampList
+     * Time in seconds corresponding to each PDU
+     * @return the timelineList
      */
-    public ArrayList<Integer> getTimestampList() {
-        return timestampList;
+    public ArrayList<Float> getTimelineList() {
+        return timelineList;
     }
     /**
      * Add PDU, typically ESPDU
@@ -126,7 +130,7 @@ public class PduTrack {
               pduList.clear();
         waypointsList.clear();
            anglesList.clear();
-        timestampList.clear();
+        timelineList.clear();
         return this;
     }
 
@@ -194,16 +198,30 @@ public class PduTrack {
         // https://stackoverflow.com/questions/16252269/how-to-sort-an-arraylist
         return this;
     }
+    /**
+     * Compute track duration in timestamp ticks
+     * @return duration in timestamp ticks between initial and final ESPDU timestamps in waypointList
+     */
+    public int getEspduCount()
+    {
+        int counter = 0;
+        for (Pdu nextPdu : pduList)
+        {
+            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 getTrackDurationTicks()
     {
-        int initialTime = -1;
-        int   finalTime = -1;
-        int    duration = -1; // used if pduList is empty
+        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 : pduList)
         {
             if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
@@ -214,16 +232,16 @@ public class PduTrack {
             }
         }
         if ((initialTime >= 0) && (finalTime >= 0))
-             duration = (finalTime - initialTime);
+             durationTicks = (finalTime - initialTime);
         if (pduList.isEmpty())
         {
-            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + duration + " due to empty pdu list");
+            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to empty pdu list");
         }
-        else if ((duration <= 0))
+        else if ((durationTicks <= 0) && (defaultWaypointInterval <= 0))
         {
-            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + duration + " due to illegal pdu list");
+            System.out.println(TRACE_PREFIX + "getTrackDuration() computed illegal duration=" + durationTicks + " due to illegal pdu list");
         }
-        return duration;
+        return durationTicks;
     }
     /**
      * Compute track duration in seconds
@@ -231,7 +249,12 @@ public class PduTrack {
      */
     public float getTrackDurationSeconds()
     {
-        float durationSeconds = getTrackDurationTicks() * 1.0f; // TODO convert
+        if (defaultWaypointInterval > 0)
+        {
+            return getEspduCount() * defaultWaypointInterval;
+        }
+        else if (getTrackDurationTicks() < 0)
+               durationSeconds = getTrackDurationTicks() * 1.0f; // TODO convert
         return durationSeconds;
     }
     /**
@@ -244,6 +267,7 @@ public class PduTrack {
         
         getWaypointsList().clear();
            getAnglesList().clear();
+            float clock = 0.0f;
         for (Pdu nextPdu : pduList)
         {
             if (nextPdu.getPduType() == DisPduType.ENTITY_STATE)
@@ -251,7 +275,16 @@ public class PduTrack {
                 EntityStatePdu espdu = (EntityStatePdu)nextPdu;
                 getWaypointsList().add(espdu.getEntityLocation());
                    getAnglesList().add(espdu.getEntityOrientation());
-                getTimestampList().add(espdu.getTimestamp());
+                   
+                   if (defaultWaypointInterval > 0)
+                   {
+                       timelineList.add(clock);
+                       clock += defaultWaypointInterval;
+                   }
+                   else
+                   {
+                       timelineList.add(espdu.getTimestamp() * 1.0f); // TODO convert
+                   }
             }
         }
         return this;
@@ -281,18 +314,63 @@ public class PduTrack {
         sb.append("<PositionInterpolator");
         sb.append(" DEF='").append(getX3dPositionInterpolatorDEF()).append("'");
         sb.append(" key='");
-        for (int i = 0; i < timestampList.size(); i++)
+        for (int i = 0; i < timelineList.size(); i++)
         {
-            float nextDuration = timestampList.get(i) * 1.0f; // TODO convert
+            float nextDuration = timelineList.get(i) * 1.0f; // TODO convert
             sb.append(String.valueOf(nextDuration));
-            if (i < timestampList.size() - 1)
+            if (i < timelineList.size() - 1)
                 sb.append(" ");
         }
         sb.append("'");
+        sb.append(" keyValue='");
+        for (int i = 0; i < waypointsList.size(); i++)
+        {
+            Vector3Double nextPosition = waypointsList.get(i);
+            sb.append(String.valueOf(nextPosition.getX()) + " " +
+                      String.valueOf(nextPosition.getY()) + " " +
+                      String.valueOf(nextPosition.getZ()));
+            if (i < waypointsList.size() - 1)
+                sb.append(",");
+        }
+        sb.append("'");
         sb.append("/>");
         
         return sb.toString();
     }
+
+    /**
+     * @return the wayPointInterval
+     */
+    public float getWayPointInterval() {
+        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 setWaypointInterval(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 < timelineList.size(); i++)
+            {
+                newTimelineList.add(clock);
+                clock += defaultWaypointInterval;
+            }
+            timelineList = newTimelineList;
+        }
+        return this;
+    }
     
     /** Self test to check basic operation, invoked by main() */
     public void selfTest()