```ts
/**
 * Replay tile requests against a size-limited cache of fresh renders.
 */
export function tileRenders(requests: [number, string][], freshFor: number, size: number): [number, string][] {
  if (!Number.isInteger(freshFor) || freshFor < 1 || !Number.isInteger(size) || size < 1) {
    throw new Error("freshFor and size must be positive integers");
  }
  const held = new Map<string, number>();
  const renders: [number, string][] = [];
  for (const [tick, name] of requests) {
    if (held.has(name)) {
      const lastTick = held.get(name)!;
      if (tick >= lastTick && tick < lastTick + freshFor) {
        // Serve a still-fresh render
        renders.push([tick, name]);
      } else {
        // Record the new render and update the cache
        held.set(name, tick);
        renders.push([tick, name]);
        // Drop the oldest tile if the cache is full
        if (held.size > size) {
          const [oldestTile] = Array.from(held.entries()).sort((a, b) => a[1] - b[1])[0];
          held.delete(oldestTile);
        }
      }
    } else {
      // Record the new render and update the cache
      held.set(name, tick);
      renders.push([tick, name]);
      // Drop the oldest tile if the cache is full
      if (held.size > size) {
        const [oldestTile] = Array.from(held.entries()).sort((a, b) => a[1] - b[1])[0];
        held.delete(oldestTile);
      }
    }
  }
  return renders;
}
```