I was able to come up with an answer. It's a bit more complicated than I would like, but it works. Let me know if anyone has a simpler idea!
Basically, I add a column "i" with the starting index. I then add a set of "NULL" rows with a fake timestamp of -1, and "i" of 0. I remove all rows with timestamp>x. I then sort the rows with timestamp in descending order, and use the second output of ismember to find the index in the new table of the row I am interested in.
Here's the starting table:
statetable =
10×3 table
    elements      state       timestamp
    ________    __________    _________
    1           IN_PROCESS    702      
    1           UPDATING      705      
    1           COMPLETE      742      
    2           IN_PROCESS     17      
    2           UPDATING       86      
    3           IN_PROCESS    926      
    4           IN_PROCESS     17      
    4           IN_PROCESS    800      
    4           IN_PROCESS    950      
    4           FAILED        975
And here's the script to generate the desired answer:
x = 86;
elements_vector = (1:max(statetable.elements))';
statetable.i = (1:height(statetable))';
nullrows = table(...
  elements_vector,...
  repmat(categorical({'NULL'}),max(statetable.elements),1),...
  -ones(max(statetable.elements),1),...
  zeros(max(statetable.elements),1),...
  'VariableNames',{'elements','state','timestamp','i'});
statetable = [statetable;nullrows];
statetable = statetable(statetable.timestamp<=x,statetable.Properties.VariableNames);
statetable = sortrows(statetable,{'elements','timestamp'},{'ascend','descend'});
[~,tmp] = ismember(elements_vector,statetable.elements);
After that, the answer is easily produced as follows:
>> IndexAtTimeX = statetable.i(tmp)
IndexAtTimeX =
       0
       5
       0
       7
>> StateAtTimeX = statetable.state(tmp)
StateAtTimeX = 
    4×1 categorical array
       NULL 
       UPDATING 
       NULL 
       IN_PROCESS

