The planner does not know milliseconds. It enumerates candidate plans and prices each one in arbitrary units assembled from a handful of constants:
seq_page_cost 1.0 one sequentially-read page
random_page_cost 4.0 one randomly-fetched page
cpu_tuple_cost 0.01 processing one row
cpu_index_tuple_cost 0.005 processing one index entry
Seq scan = pages x seq_page_cost + rows x cpu_tuple_cost. A fixed price, independent of how many rows match.
Index scan = descent + matched x (random_page_cost + cpu costs). A per-matched-row price.
One is flat, the other has slope. They cross, and the planner picks whichever is lower at the estimated row count. That is the whole of plan selection. The cost=X..Y in EXPLAIN is exactly these numbers: startup cost, then total cost.
Those are Postgres's shipped defaults. The exercise works in the same units multiplied by 100 so nothing depends on floats, and rounds cpu_index_tuple_cost up to one unit — it is the smallest term in the sum and moves the crossover by under two rows in a hundred thousand.
