docs: public SubEpochReturn<O> trainSubEpoch(O sObs, int nstep) {
  
          synchronized (getAsyncGlobal()) {
              current.copy(getAsyncGlobal().getCurrent());
          }
          Stack<MiniTrans<Integer>> rewards = new Stack<>();
  
          O obs = sObs;
          Policy<O, Integer> policy = getPolicy(current);
  
          Integer action;
          Integer lastAction = null;
          IHistoryProcessor hp = getHistoryProcessor();
          int skipFrame = hp != null ? hp.getConf().getSkipFrame() : 1;
  
          double reward = 0;
          double accuReward = 0;
          int i = 0;
          while (!getMdp().isDone() && i < nstep * skipFrame) {
  
              INDArray input = Learning.getInput(getMdp(), obs);
              INDArray hstack = null;
  
              if (hp != null) {
                  hp.record(input);
              }
  
              //if step of training, just repeat lastAction
              if (i % skipFrame != 0 && lastAction != null) {
                  action = lastAction;
              } else {
                  hstack = processHistory(input);
                  action = policy.nextAction(hstack);
              }
  
              StepReply<O> stepReply = getMdp().step(action);
              accuReward += stepReply.getReward() * getConf().getRewardFactor();
  
              //if it's not a skipped frame, you can do a step of training
              if (i % skipFrame == 0 || lastAction == null || stepReply.isDone()) {
                  obs = stepReply.getObservation();
  
                  if (hstack == null) {
                      hstack = processHistory(input);
                  }
                  INDArray[] output = current.outputAll(hstack);
                  rewards.add(new MiniTrans(hstack, action, output, accuReward));
  
                  accuReward = 0;
              }
  
              reward += stepReply.getReward();
  
              i++;
              lastAction = action;
          }
  
          //a bit of a trick usable because of how the stack is treated to init R
          INDArray input = Learning.getInput(getMdp(), obs);
          INDArray hstack = processHistory(input);
  
          if (hp != null) {
              hp.record(input);
          }
  
          if (getMdp().isDone() && i < nstep * skipFrame)
              rewards.add(new MiniTrans(hstack, null, null, 0));
          else {
              INDArray[] output = null;
              if (getConf().getTargetDqnUpdateFreq() == -1)
                  output = current.outputAll(hstack);
              else synchronized (getAsyncGlobal()) {
                  output = getAsyncGlobal().getTarget().outputAll(hstack);
              }
              double maxQ = Nd4j.max(output[0]).getDouble(0);
              rewards.add(new MiniTrans(hstack, null, output, maxQ));
          }
  
          getAsyncGlobal().enqueue(calcGradient(current, rewards), i);
  
          return new SubEpochReturn<O>(i, obs, reward, current.getLatestScore());
      }