I was undecided on joining the clique, but curious. I didn’t want to be a sucker if someone (possibly the clique organizer) found a way to betray it. I sent out feelers, and Vanilla_Cabs shared the clique bot code with me.
I saw a way to defect against the clique. I think that’s when I decided to do so, though I may have had the idea in my head beforehand. I would call my entry “bastardBot” and it would be glorious. I told Vanilla_Cabs I was in. They asked if the code was airtight. “I don’t see anything I want to flag.”
Someone else found that same bug, and was more honest than I. I spent some time trying to work around the fix, but couldn’t see anything. I tried to get Vanilla_cabs to put a new hole in, under the pretext that I wanted some code at the top level—this was true, but it was only marginally useful. I couldn’t think of any new holes that wouldn’t be really freaking obvious, so instead I tried being vague about my requirements to see if they’d suggest something I could use, but they didn’t. Eventually we just settled on “the exact line foo = 'bar' is permitted as an exception”, and I didn’t see what I could do with that.
Later, lsusr told me that that line would get me disqualified. I didn’t say anything, in the hopes some clique member would wonder what it was for, include it in their bot just in case, and get disqualified.
I feel a little bad about all this, and hope Vanilla_cabs has no hard feelings.
My backup plan was: don’t let anyone simulate me, and get them disqualified if they try. (New name: “incomprehensibot”.) “jailbreaker.py” shows my tricks here. Defense seemed more effective than offense, and I didn’t think I could safely simulate my opponent, and especially not do so safely and usefully within the time limits, so I gave up on the idea. As for my actual moves, I didn’t have much in mind. After rereading (or at least reskimming) “the Darwin pregame” I settled on this:
After the showdown round, start off with something like Zvi’s “I’ll let you do better than me, but you could do even better by cooperating with me”. Gradually move towards “I won’t let you do better than me” as time progresses; if my opponent had more than a certain number of points than me, I’d refuse to play less than 3. (I chose the number of points based on expecting 550 turns on average, and gave it a minimum of 5 to allow some coordination dance early on.) Early on, skew towards playing 2 initially; if opponents randomize between 2 and 3, and pick 3 with probability >= 1⁄3, then 2 maximizes my score. Later, skew towards playing 3 initially, which increases my probability of beating my opponent.
“payload.py” shows my approach here. I modelled it off the early-round CloneBot moves against non-clones. If last round had been a total of 5, I’d play their last move. If it had been 4, I’d do the same, but maybe add a little. If it had been more, I’d repeat my own move, but maybe subtract one. In the 5 and >5 cases, I had some “pushing” behaviour to see if I could exploit them: if I haven’t had a chance to push yet, or if pushing had worked well (or seemed like it would have worked well) in the past, or if I just hadn’t tried it recently, I’d try to take one more point than I really had any right to. I didn’t do that in the <5 case because that situation was my only source of randomness, which seemed important somehow.
(I’m a bit confused about, if the last-round total isn’t five, should I base off my own previous move or theirs? My decisions here weren’t principled.)
If this made me play 0 (I dunno if it ever would), I’d make it a 1. If it made me play less than 3, and I was too far behind (depending on round), I’d make it a 3.
Just before I went to bed Saturday night, someone sent a message to the clique group saying not to try to break simulators. Because if a clique member simulates us and gets disqualified, the tournament is restarted and the clique is smaller. That was completely right, and I felt stupid for not thinking of it sooner.
I still decided to ignore it, because I thought the game would be small enough that “fewer opponents” was better than “bigger clique”. Overnight someone else said they’d already submitted a simulation-breaker, so I dunno if anyone ended up playing a simulator.
Right towards the end I started doing numerical analysis, because early on I was too enamoured with my own cleverness to notice what a good idea it was. I didn’t have time to do anything thoroughly, but based on running my paload against ThreeBot (which gets 148-222 early, 10-15 late) I reduced my exploitability ramp-down from 100 rounds (chosen fairly arbitrarily) to 20 (still fairly arbitrary). Come to think of it, I don’t think I compared “what proportion of the pool do I need to eventually win” between my early and late game behaviors.
It would have been interesting to have some kind of logging such that my bot could report “I think I’m being simulated right now, and this is how I know” and afterwards lsusr could me how often that happened. I assume that would be significant work for lsusr to set up though, and it adds attack surface.
Predictions:
I win: 20%.
A CloneBot wins: 75%.
At least one clique member submits a non-CloneBot (by accident or design): 60%.
At least one clique member fails to submit anything: 60%.
At least one bot tries to simulate me after the showdown and doesn’t get disqualified: 10%.
At least one bot tries to simulate me after the showdown and succeeds: 5%.
*At least one bot tries to simulate me after the showdown and doesn’t get disqualified: 10%.
At least one bot tries to simulate me after the showdown and succeeds: 5%.
I now think these were overconfident. I think it would be fairly easy to simulate incomprehensibot safely; but hard to simulate incomprehensibot in a way that would be both safe and generically useful.
The difficulty with simulating is that you need to track your opponent’s internal state. If you just call MyOpponentBot(round).move(myLastMove) you’ll be simulating “what does my opponent do on the first turn of the game, if it gets told that my last move was...”. If you do this against incomprehensibot, and myLastMove is not None, incomprehensibot will figure out what’s up and try to crash you.
So at the beginning, you initialize self.opponent = MyOpponentBot(round). And then every turn, you call self.opponent.move(myLastMove) to see what it’s going to do next. I don’t expect incomprehensibot to figure this out.
But if your opponent has any random component to it, you want to do that a couple of times at least to see what’s going to happen. But if you call that function multiple times, you’re instead simulating “what does my opponent do if it sees me play myLastMove several times in a row”. And so you need to reset state using deepcopy or multiprocessing or something, and incomprehensibot has ways to figure out if you’ve done that. (Or I suppose you can just initialize a fresh copy and loop over the game history running .move(), which would be safe.)
But actually, the simple “initialize once and call .move() once per turn” isn’t obviously terrible? Especially if you keep track of your predictions and stop paying attention to them once they deviate more than a certain amount (possibly zero) from reality. And Taleuntum’s bot might catch that, I’m not sure, but I think Incomprehensibot wouldn’t.
I think at some point I decided basically no one would do that, and then at some other point I forgot that it was even a possibility? But I now think that was silly of me, and someone might well do that and last until the showdown round. Trying to put a number on that would involve thinking in more depth than I did for any of my other predictions, so I’m not going to try, just leave this note.
The links you posted do not work for me. (Nevermind)
Wow, you are really confident in you winning. There are 10 players in the clique, so even if there are no players outside the clique (a dubious assumption) a priori there is 10% chance. If I had money I would bet with you.
I also think there is a good chance that a CloneBot wins. 10 possible member is a good number imo. i would say 80%.
I would say 70% for the (possibly accidental) betrayal.
Without seeing your jailbreak.py I can’t say how likely that others are able to simulate you.
Conditioned on “any CloneBot wins” I’ve given myself about 25%.
10% in that conditional would definitely be too low—I think I have above-baseline chances on all of “successfully submit a bot”, “bot is a CloneBot” and “don’t get disqualified”. I think I expect at least three to fall to those hurdles, and five wouldn’t surprise me. And of the rest, I still don’t necessarily expect most of them to be very serious attempts.
By “act out” I mean it’s a bot that’s recognized as a CloneBot by the others but doesn’t act like one—most likely cooperating with non-clones, but not-cooperating with clones would also count, it would just be silly as far as I can tell. I also include such a bot as a CloneBot for the 75%.
Updated with a link to my code. I also put yours in to see how we’d fare against each other one-on-one—from quick experimentation, looks like we both get close to 2.5 points/turn, but I exploit you for approximately one point every few hundred turns, leaving me the eventual victor. :D I haven’t looked closely to see where that comes from.
Of course too much depends on what other bots are around.
They asked if the code was airtight. “I don’t see anything I want to flag.”
And I saw right through that my friend :D
As I said in my reply to Taleuntum, I left the weakness as a test to find someone I could trust to find sneakier weaknesses. Of you three who saw the code, only Lanrian reported. “I don’t see anything I want to flag.” That’s cute. To be more accurate, I wasn’t sure you were hiding a security flaw, but I didn’t have to be sure since either way meant I couldn’t entrust you with the task. And the wording left me thinking you were hiding a security flaw with 80% credence. I thought about asking “Did you see anything worth flagging?”, but decided against it.
Later, lsusr told me that that line would get me disqualified. I didn’t say anything, in the hopes some clique member would wonder what it was for, include it in their bot just in case, and get disqualified.
I feel a little bad about all this, and hope Vanilla_cabs has no hard feelings.
Not at all, I just feel like I’ve dodged a big bullet. How come that line would get someone disqualified? Has lsusr been more specific?
Eventually we just settled on “the exact line foo = ‘bar’ is permitted as an exception”, and I didn’t see what I could do with that.
Later, lsusr told me that that line would get me disqualified. I didn’t say anything, in the hopes some clique member would wonder what it was for, include it in their bot just in case, and get disqualified.
I thought it would be harmless and that there was some chance (even though it would make more sense to read some codeword directly from the payload) that someone would be using it as a recognition signal to boost a chosen clique member’s bot using a non clique member’s bot.
Is it really disqualifiable? I am not using foo for anything else other than setting it.
The parent comment comes from a private conversation between me and philh in which I conveyed erroneous information. The mistake is my fault—not philh’s. A line such as foo = 'bar' is legal in some contexts. A line which uses the global namespace to preserve information between class instances is illegal.
Disqualifying players for things they obviously wouldn’t do if they knew the rules of the game seems pretty cruel. I hope isusr just deletes that line for you.
Setting constants into the global state of your own file is fine. What I care about is whether you’re setting variables into the global state for the purpose of preserving information between bot instantiations or otherwise messing with the game engine. simon’s bot clearly does neither.
I confess I’m a bit confused, I thought in our PM conversation I was fairly explicit that that’s what I was asking about, and you were fairly explicit that it was forbidden?
It’s not a big deal—even if this was forbidden I’d think it would be totally fine not to disqualify simon, and I still don’t actually expect it to have been useful for me.
In my private conversation with you (philh) I stated “Writing variables to the global namespace is forbidden.” I then doubled down on the error by stating “Yes. All data must be saved in a class instance.”
I apologize for the error. What I should have written was that using the global namespace to get around information restrictions is illegal but that writing constants to your module’s global namespace in a way that does not evade information restrictions is fine.
Will post a link to a github repo with my code later today (when I’m not meant to be working), but for now, here’s my thought processes.
(Edit: my code is here. My entry is in incomprehensibot.)
General strategy:
I was undecided on joining the clique, but curious. I didn’t want to be a sucker if someone (possibly the clique organizer) found a way to betray it. I sent out feelers, and Vanilla_Cabs shared the clique bot code with me.
I saw a way to defect against the clique. I think that’s when I decided to do so, though I may have had the idea in my head beforehand. I would call my entry “bastardBot” and it would be glorious. I told Vanilla_Cabs I was in. They asked if the code was airtight. “I don’t see anything I want to flag.”
Someone else found that same bug, and was more honest than I. I spent some time trying to work around the fix, but couldn’t see anything. I tried to get Vanilla_cabs to put a new hole in, under the pretext that I wanted some code at the top level—this was true, but it was only marginally useful. I couldn’t think of any new holes that wouldn’t be really freaking obvious, so instead I tried being vague about my requirements to see if they’d suggest something I could use, but they didn’t. Eventually we just settled on “the exact line
foo = 'bar'
is permitted as an exception”, and I didn’t see what I could do with that.Later, lsusr told me that that line would get me disqualified. I didn’t say anything, in the hopes some clique member would wonder what it was for, include it in their bot just in case, and get disqualified.
I feel a little bad about all this, and hope Vanilla_cabs has no hard feelings.
My backup plan was: don’t let anyone simulate me, and get them disqualified if they try. (New name: “incomprehensibot”.) “jailbreaker.py” shows my tricks here. Defense seemed more effective than offense, and I didn’t think I could safely simulate my opponent, and especially not do so safely and usefully within the time limits, so I gave up on the idea. As for my actual moves, I didn’t have much in mind. After rereading (or at least reskimming) “the Darwin pregame” I settled on this:
After the showdown round, start off with something like Zvi’s “I’ll let you do better than me, but you could do even better by cooperating with me”. Gradually move towards “I won’t let you do better than me” as time progresses; if my opponent had more than a certain number of points than me, I’d refuse to play less than 3. (I chose the number of points based on expecting 550 turns on average, and gave it a minimum of 5 to allow some coordination dance early on.) Early on, skew towards playing 2 initially; if opponents randomize between 2 and 3, and pick 3 with probability >= 1⁄3, then 2 maximizes my score. Later, skew towards playing 3 initially, which increases my probability of beating my opponent.
“payload.py” shows my approach here. I modelled it off the early-round CloneBot moves against non-clones. If last round had been a total of 5, I’d play their last move. If it had been 4, I’d do the same, but maybe add a little. If it had been more, I’d repeat my own move, but maybe subtract one. In the 5 and >5 cases, I had some “pushing” behaviour to see if I could exploit them: if I haven’t had a chance to push yet, or if pushing had worked well (or seemed like it would have worked well) in the past, or if I just hadn’t tried it recently, I’d try to take one more point than I really had any right to. I didn’t do that in the <5 case because that situation was my only source of randomness, which seemed important somehow.
(I’m a bit confused about, if the last-round total isn’t five, should I base off my own previous move or theirs? My decisions here weren’t principled.)
If this made me play 0 (I dunno if it ever would), I’d make it a 1. If it made me play less than 3, and I was too far behind (depending on round), I’d make it a 3.
Just before I went to bed Saturday night, someone sent a message to the clique group saying not to try to break simulators. Because if a clique member simulates us and gets disqualified, the tournament is restarted and the clique is smaller. That was completely right, and I felt stupid for not thinking of it sooner.
I still decided to ignore it, because I thought the game would be small enough that “fewer opponents” was better than “bigger clique”. Overnight someone else said they’d already submitted a simulation-breaker, so I dunno if anyone ended up playing a simulator.
Right towards the end I started doing numerical analysis, because early on I was too enamoured with my own cleverness to notice what a good idea it was. I didn’t have time to do anything thoroughly, but based on running my paload against ThreeBot (which gets 148-222 early, 10-15 late) I reduced my exploitability ramp-down from 100 rounds (chosen fairly arbitrarily) to 20 (still fairly arbitrary). Come to think of it, I don’t think I compared “what proportion of the pool do I need to eventually win” between my early and late game behaviors.
It would have been interesting to have some kind of logging such that my bot could report “I think I’m being simulated right now, and this is how I know” and afterwards lsusr could me how often that happened. I assume that would be significant work for lsusr to set up though, and it adds attack surface.
Predictions:
I win: 20%.A CloneBot wins: 75%.
At least one clique member submits a non-CloneBot (by accident or design): 60%.At least one clique member fails to submit anything: 60%.
At least one bot tries to simulate me after the showdown and doesn’t get disqualified: 10%.At least one bot tries to simulate me after the showdown and succeeds: 5%.At least one CloneBot manages to act out: 5%.
I get disqualified: 5%.I now think these were overconfident. I think it would be fairly easy to simulate incomprehensibot safely; but hard to simulate incomprehensibot in a way that would be both safe and generically useful.
The difficulty with simulating is that you need to track your opponent’s internal state. If you just call
MyOpponentBot(round).move(myLastMove)
you’ll be simulating “what does my opponent do on the first turn of the game, if it gets told that my last move was...”. If you do this against incomprehensibot, andmyLastMove
is notNone
, incomprehensibot will figure out what’s up and try to crash you.So at the beginning, you initialize
self.opponent = MyOpponentBot(round)
. And then every turn, you callself.opponent.move(myLastMove)
to see what it’s going to do next. I don’t expect incomprehensibot to figure this out.But if your opponent has any random component to it, you want to do that a couple of times at least to see what’s going to happen. But if you call that function multiple times, you’re instead simulating “what does my opponent do if it sees me play myLastMove several times in a row”. And so you need to reset state using
deepcopy
or multiprocessing or something, and incomprehensibot has ways to figure out if you’ve done that. (Or I suppose you can just initialize a fresh copy and loop over the game history running.move()
, which would be safe.)But actually, the simple “initialize once and call
.move()
once per turn” isn’t obviously terrible? Especially if you keep track of your predictions and stop paying attention to them once they deviate more than a certain amount (possibly zero) from reality. And Taleuntum’s bot might catch that, I’m not sure, but I think Incomprehensibot wouldn’t.I think at some point I decided basically no one would do that, and then at some other point I forgot that it was even a possibility? But I now think that was silly of me, and someone might well do that and last until the showdown round. Trying to put a number on that would involve thinking in more depth than I did for any of my other predictions, so I’m not going to try, just leave this note.
The links you posted do not work for me.(Nevermind)Wow, you are really confident in you winning. There are 10 players in the clique, so even if there are no players outside the clique (a dubious assumption) a priori there is 10% chance. If I had money I would bet with you.
I also think there is a good chance that a CloneBot wins. 10 possible member is a good number imo. i would say 80%.
I would say 70% for the (possibly accidental) betrayal.
Without seeing your jailbreak.py I can’t say how likely that others are able to simulate you.
What does “act out” mean in this context?
Conditioned on “any CloneBot wins” I’ve given myself about 25%.
10% in that conditional would definitely be too low—I think I have above-baseline chances on all of “successfully submit a bot”, “bot is a CloneBot” and “don’t get disqualified”. I think I expect at least three to fall to those hurdles, and five wouldn’t surprise me. And of the rest, I still don’t necessarily expect most of them to be very serious attempts.
By “act out” I mean it’s a bot that’s recognized as a CloneBot by the others but doesn’t act like one—most likely cooperating with non-clones, but not-cooperating with clones would also count, it would just be silly as far as I can tell. I also include such a bot as a CloneBot for the 75%.
Updated with a link to my code. I also put yours in to see how we’d fare against each other one-on-one—from quick experimentation, looks like we both get close to 2.5 points/turn, but I exploit you for approximately one point every few hundred turns, leaving me the eventual victor. :D I haven’t looked closely to see where that comes from.
Of course too much depends on what other bots are around.
And I saw right through that my friend :D
As I said in my reply to Taleuntum, I left the weakness as a test to find someone I could trust to find sneakier weaknesses. Of you three who saw the code, only Lanrian reported.
“I don’t see anything I want to flag.” That’s cute.To be more accurate, I wasn’t sure you were hiding a security flaw, but I didn’t have to be sure since either way meant I couldn’t entrust you with the task. And the wording left me thinking you were hiding a security flaw with 80% credence. I thought about asking “Did you see anything worth flagging?”, but decided against it.Not at all, I just feel like I’ve dodged a big bullet. How come that line would get someone disqualified? Has lsusr been more specific?
Well played!
I fell for
I thought it would be harmless and that there was some chance (even though it would make more sense to read some codeword directly from the payload) that someone would be using it as a recognition signal to boost a chosen clique member’s bot using a non clique member’s bot.
Is it really disqualifiable? I am not using foo for anything else other than setting it.
Putting data in global state is forbidden, yeah, even if you don’t do anything with it. I was a bit surprised.
Just to be clear, this would only be forbidden if you put it at the top level. If you put it in your class it would be fine. So
The parent comment comes from a private conversation between me and philh in which I conveyed erroneous information. The mistake is my fault—not philh’s. A line such as
foo = 'bar'
is legal in some contexts. A line which uses the global namespace to preserve information between class instances is illegal.Yeah, I put it in at top level.
Disqualifying players for things they obviously wouldn’t do if they knew the rules of the game seems pretty cruel. I hope isusr just deletes that line for you.
simon will not be disqualified.
Thanks!
Setting constants into the global state of your own file is fine. What I care about is whether you’re setting variables into the global state for the purpose of preserving information between bot instantiations or otherwise messing with the game engine. simon’s bot clearly does neither.
I confess I’m a bit confused, I thought in our PM conversation I was fairly explicit that that’s what I was asking about, and you were fairly explicit that it was forbidden?
It’s not a big deal—even if this was forbidden I’d think it would be totally fine not to disqualify simon, and I still don’t actually expect it to have been useful for me.
In my private conversation with you (philh) I stated “Writing variables to the global namespace is forbidden.” I then doubled down on the error by stating “Yes. All data must be saved in a class instance.”
I apologize for the error. What I should have written was that using the global namespace to get around information restrictions is illegal but that writing constants to your module’s global namespace in a way that does not evade information restrictions is fine.