The rules are short. When a node does any local event it increments its own slot by one. When a node sends a message it first increments its own slot, then attaches a copy of its whole vector to the message. When a node receives a message it takes the element-wise maximum of its own vector and the incoming vector, then increments its own slot by one.
Taking the maximum on receipt is the key move. It folds in everything the sender knew, so the receiver's vector now reflects all events that causally precede this message. Incrementing your own slot after merging keeps your local count honest as the source of truth for your own entry.
Walk through it. A starts at [0,0], does a write, becomes [1,0], and sends that to B. B was at [0,0]; it merges to get [1,0], then bumps its own slot to [1,1]. If B now receives a later message from A carrying [3,0], B would merge to [3,1] and bump to [3,2]. Anyone later comparing vectors can reconstruct exactly which events are ancestors of which.
The cost is size. The vector grows with the number of nodes, so a cluster of 500 nodes carries a 500-entry vector on every value. Systems prune entries for nodes that have left, or switch to dotted version vectors and similar refinements to keep the metadata bounded.