A creative use of 'max-src-conn' and 'overload <table>' could possibly
provide what you want. See pf.conf(5) for details.
As trigger, you use a source-tracking rule on the internal interface.
When a LAN host triggers the rule limit, its (un-NATed) source address
will be added to the table.
table <free_lan_host> persist
pass in on $int_if inet proto tcp from any to $free_net_host port www
keep state (source-track rule, max-src-conn 1, overload <free_lan_host>)
Instead of using the table to block further connections (the traditional
use for this feature), you can use it in the rdr rules.
rdr on $ext_if inet proto udp from any to $ext_if port $free_dst_udp_ports
-> <free_lan_host>
As long as the table only contains zero or more addresses, things should
work as expected. There is no automatic purging of the table, you'll
have to expire table entries yourself, like with a cron job
*/5 * * * * pfctl -t free_lan_host -T flush
In -current, there's a command to expire unused addresses explicitely
* * * * * pfctl -t free_lan_host -T expire 60
How well that works depends on how often/quickly you want to switch
$free_lan_host.
Daniel