docs: private void step(String command, Socket socket, DataInputStream din) throws IOException {
  
          String actions = command.substring(stepTagLength, command.length() - (stepTagLength + 2));
          int options =  Character.getNumericValue(command.charAt(stepTagLength - 2));
          boolean withTurnkey = options < 2;
          boolean withInfo = options == 0 || options == 2;
          // TCPUtils.Log(Level.FINE,"Command (step action): " + actionCommand + " options " + options);
  
          byte[] stepTurnKey;
          if (withTurnkey) {
              int hdr;
              hdr = din.readInt();
              stepTurnKey = new byte[hdr];
              din.readFully(stepTurnKey);
          } else {
              stepTurnKey = new byte[0];
          }
  
          DataOutputStream dout = new DataOutputStream(socket.getOutputStream());
          double reward = 0.0;
          boolean done;
          byte[] obs;
          String info = "";
          byte[] currentTurnKey;
          byte[] nextTurnKey;
          boolean sent = false;
  
          lock.lock();
          try {
  
              done = envState.done;
  
              obs = getObservation(done);
  
              // If done or we have new observation and it's our turn then submit command and pick up rewards.
  
              currentTurnKey = envState.turnKey.getBytes();
              boolean outOfTurn = true;
              nextTurnKey = currentTurnKey;
  
              if (!done && obs.length > 0 && actions != "") {
                  // CurrentKey   StepKey     Action (WithKey)    nextTurnKey     outOfTurn
                  // ""           ""          Y                   Current         N
                  // ""           X           N                   Step            Y
                  // X            0           N                   Current         Y
                  // X            X           Y (WK)              Current         N
                  // X            Y           N                   Current         Y
  
                  // TCPUtils.Log(Level.FINE, "current TK " + envState.turnKey + " step TK " + new String(stepTurnKey));
                  if (currentTurnKey.length == 0) {
                      if (stepTurnKey.length == 0) {
                          if (actions.contains("\n")) {
                              String[] cmds = actions.split("\\n");
                              for(String cmd : cmds) {
                                  envState.commands.add(cmd);
                              }
                          } else {
                              if (!actions.isEmpty())
                                  envState.commands.add(actions);
                          }
                          outOfTurn = false;
                          sent = true;
                      } else {
                          nextTurnKey = stepTurnKey;
                      }
                  } else {
                      if (stepTurnKey.length != 0) {
                          if (Arrays.equals(currentTurnKey, stepTurnKey)) {
                              // The step turn key may later still be stale when picked up from the command queue.
                              envState.commands.add(new String(stepTurnKey) + " " + actions);
                              outOfTurn = false;
                              envState.turnKey = "";
                              envState.lastTurnKey = new String(stepTurnKey);
                              sent = true;
                          }
                      }
                  }
              }
  
              if (done || (obs.length > 0 && !outOfTurn)) {
                  // Pick up rewards.
                  reward = envState.reward;
                  envState.reward = 0.0;
                  if (withInfo) {
                      info = envState.info;
                      envState.info = "";
                      if (info.isEmpty() && !done) {
                          try {
                              cond.await(COND_WAIT_SECONDS, TimeUnit.SECONDS);
                          } catch (InterruptedException ie) {
                          }
                          info = envState.info;
                          envState.info = "";
                          if (envState.obs != null && envState.obs != obs) {
                              // Later observation.
                              obs = envState.obs;
                          }
                      }
                  }
                  envState.obs = null;
              }
          } finally {
              lock.unlock();
          }
  
          dout.writeInt(obs.length);
          dout.write(obs);
  
          dout.writeInt(BYTES_DOUBLE + 2);
          dout.writeDouble(reward);
          dout.writeByte(done ? 1 : 0);
          dout.writeByte(sent ? 1 : 0);
  
          if (withInfo) {
              byte[] infoBytes = info.getBytes(utf8);
              dout.writeInt(infoBytes.length);
              dout.write(infoBytes);
          }
  
          if (withTurnkey) {
              dout.writeInt(nextTurnKey.length);
              dout.write(nextTurnKey);
          }
          dout.flush();
      }