Home Join How To Play Rules Judgments Glossary Calendar Archives Herald

A Completely Formal Nomic

Submitted in partial fulfilment of the

requirements for the degree of Bachelor of Nomic. This Thesis is associated with the recent Proposal "A B.N. for favor".

Player Favor, Agora Nomic

Introduction

Historically, many of the scams and CFJs in Agora Nomic have hinged on matters of word-meaning. Everyday language is notoriously ambiguous and dependant on assumptions about speaker and listener intent that are not logically well-founded; they require the application of what is often called "common sense" (a loose and imperfectly-shared collecion of default assumptions, word-usage rules, and so on).

The Rules and Customs of Agora Nomic have historically had an ambivalent attitude towards common sense. Judges have sometimes been allowed or required to exercise it when the Rules are not clear. Some Players have cited the imperfect consensus on matters of common sense in borderline cases, and tried to reduce or eliminate the Rules' dependence on it.

CFJs and scams based on word usage are welcomed and relished by some Players, and despised by others. Some have accused the community of making scams impossible by encouraging Judges to block them by quibbles on word meaning; others have claimed that without such quibbles, most scams would be impossible in the first place.

It would be illuminating to consider what a Nomic game might be like that was wholely lacking in disputes and disagreements about word usage and meaning, or about common sense or other broad subjective notions. Such a game would be interesting by virtue of the contrasting light it would throw on Agora and similar Nomics, as well as potentially leading to interesting games in its own right.

So

The idea is to design a completely formal Game, one in which there are no rules that have subjective components, or are otherwise open to disputes about common sense or the meanings of words. The game should also be as Nomic-like as possible, given that restriction, in that the rules of the game should be represented within, and changeable by moves of, the game.

This will not be "real" Nomic unless we are wildly more successful than I think we will be. Because we have to choose a comparatively simple formal framework (in order to keep it comprehensible), it's unlikely that we'll be able to get as "meta" as non-formal Nomic can get. It will also, of course, not have the fun of Judgement and dispute about word meanings and intent and sense and reference and all that that most Nomics offer. This is intended to be a different game, albeit somewhat inspired by the usual Nomic. But there's no claim that it's better than any other Nomic, or truer to any particular ideal, or anything like that.

There will, of course, have to be an interface between the formal system in which the rules sit and the players, who are out in the physical world. This interface will be referred to as the Mechanics of the game, and these will be subject to all sorts of disputes and things. But they are not part of the game itself; any issues that come up about the Mechanics ("I didn't make that move; someone must have forged the e-mail!") will be extra-game issues, and will not be expressed or resolved in the gamestate.

Similarly, the language in which the game is expressed will be subject to change (and in particular to expansion) as the game goes on, but this again is an extra-game process. As new verbs or conditions or whatever are added to the language, we will just have discovered new parts of the formal space that the game is exploring. The status of the language of the game is not contained in the game itself. This is another limitation that nonformal Nomic does not, at least in theory, suffer from. I suspect, however, that a strong case could be made for the claim that any nonformal Nomic played by humans will in fact be subject to the same sort of limitation, in that humans, being finite entities instantiated in a limited universe, cannot ultimately escape all bounds of language themselves.

Relationship to Agora Nomic

Formal Nomic can serve Agora Nomic as a thought-pump, if nothing else. If some actual or contemplated feature of Agora Nomic would be especially hard or easy to add to a hypothetical game of Formal Nomic, or would require diddling with the Mechanics, that says something about the nature of the feature. If two apparently-similar ways of embodying within Agora the same basic idea turn out to have very different implications for a hypothetical game of Formal Nomic, that says something about the differences between them.

Formal Nomic might also be a useful, or instructive, or amusing subgame (probably a Contest) within Agora Nomic. One Player would implement (either automatically, or by hand) the Mechanics, and choose an Initial Set; the other Players in the jurisdiction of the subgame would submit input to the Mechanics, changing the state of the subgame. Winning the subgame could mean being awarded the subgame's Treasury, or whatever.

Details / Examples

The rest of this paper consists of a worked-out example of one completely formal Nomic, including a description of the mechanics, and an Inital Set (that is, an initial object-pool). It is of course only one example; the basic objective could be accomplished in many other ways. For instance, if all the Players were willing to be computer programmers, the ruleset and gamestate could be expressed directly as computer program texts and variable values, at a considerable increase in power, and a considerable cost in difficulty of play.

For this example, the incoming interface is relatively simple: when a piece of mail comes in for the "server", it's parsed into one or more objects in the system. Each object has a Type of "move", a Sender and Timestamp filled in from the envelope, a randomly-generated BatchNumber that's the same for all the objects created by the same piece of mail, and other fields filled in from the body of the message. For instance, a request to register might look like:

   From: Matt Zimola 
   Date: 14 Aug 1995, 12:36:13 UTC
   Subject: to the Mech server

   REGISTER MattZ

and the corresponding object that gets created might be:

   objectId: 127
   batchId: 114
   moveTimeStamp: 1995/08/14 12:36:13
   moveSender: [email protected]
   type: move
   subtype: register
   nickname: MattZ

"REGISTER MattZ" is syntactic sugar, to make a common operation easier. In its full generality, the incoming Mechanics would allow the sender of the mail to specify any attributes and values e wants to (except type, objectId, batchId, and so on).

On the other end, there will be a small set of verbs that cause extra-game side-effects; primarily mail-sending to let the physical-world players know what's going on. But these side-effects are not defined in or modifiable by the game rules; they are the Mechanics, not the Rules. From the point of view of the game-state itself, the sendFoo() verbs will be no-ops.

A rule is just another object, with Type "rule", an "if" attribute which is some boolean function of the gamestate, and a "then" attribute which is some sequence of verbs. It also has a ruleOrder, to determine which rules get to look at the game-state first, and probably various other housekeeping numbers. (More generally, we can have a special engineSettings object which would tell the engine which type of objects are really rules; that would normally be set to "rule", but could be diddled with for various special effects.)

The sequence of events in the game is as follows. Note that this is again a thing that isn't part of, or changable by, the Rules, and this is another un-Nomic-like limitation. It may be possible to make some or all of this stuff adjustable within the game also, with some effort. (Note that that effort would have to be done outside the game, in the Mechanics, to enable laters efforts within the game.)

There are all sorts of "programming" issues involved in making these things actually work. When a rule fires and changes the game-state, for instance, it will almost always have to make sure that the change it makes is such that it won't fire again for the same reasons, since its change will cause a restart, and if no rule with a lower ruleOrder fires, it will see the game-state again just the way it left it. So for instance a rule that gives a player a point because of some condition should make sure to also make the condition false, otherwise it may give the player an infinite number of points, one at a time! (Another aspect of the Mechanics will presumably be an extra-Game meta-rule like "if the game-state goes into a verifiable loop, the last player to submit a move wins".)

One way to manage this is to have a rule simply delete the object that caused it to fire; this is sensible in some cases, although it will often be better to simply mark it in some way (set the processedByRule47 attribute to T, for instance), rather than deleting it, so that other rules with higher ruleOrders will get a chance to act on it as well.

Below is a sample Initial State. It includes registration (but not deregistration or On Hold), proposals (which may contain multiple Rule Changes), voting (but not multiple votes, or changing votes), quorum, simple majority (AIs and MIs are quite doable, but are not in this Initial State), points for voting and having your proposals pass, and a simple Win on points (which ends the game). Kudos, degrees, Win Tokens, Extra Votes, the Potato, and some other familiar Agora concepts would fit into this set in reasonably simple ways.

Some other concepts are much harder; for instance, the idea of sub-rulesets that only apply to some Players, as in Group Ordinances and Contest Regulations. Platonic sub-rules are especially hard to formalize. For instance, if a Group Ordinance says that in some circumstances a Group Member shall transfer fifteen Marks to a non-Member, that's acceptable. If it says the opposite, that some non-Member shall transfer fifteen Marks to some Member, that isn't. In both cases, both a Member and non-Member appear in the subrule; formalizing the difference between the two is a subject for further study.

I haven't actually implemented any of this as a computer program, and I'm not sure that I have any interest in doing so. But I hope it will spur some interesting discussion nonetheless...

A note on scams: an amusing proof that scams are entirely possible in this formal Nomic. In the first draft of this Initial State, Rule 16 didn't set the "cleanup" attribute of incoming votes to F. So if you submitted a vote, and set "cleanup" to T in the move object, you'd get your point for voting, but the vote would be destroyed by the Sweeper, so you could do it again. And again. And again. Getting a point each time, and winning at will. Easy to fix, but illustrative! (In the very first version of Rule 16, it didn't even have the check to ensure that the proposal ID was valid, making it easy to win by voting for a hundred non-existent proposals.)

Anyway:

Basic attributes of objects:

  objectId - a number, used as a handle for talking
     about an object, assigned sequentially by the system
     when the object is created.  Match clauses always
     find the matching object with the lowest objectId.
  type - tells what it is; the incoming interface produces only
     objects of type "move".  The rule engine gets its settings
     from the first objects of type "engineControls", and
     runs only objects whose type is the same as the runType
     attribute of engineControls. All other types have meaning only to
     rules.  A basic type in the initial set is "player".

Move attributes set by the incoming interface (these attributes are always assigned; any and all other attributes besides Type can be set by the incoming mail explicitly):

  moveSender - [email protected] that sent the mail
  moveTimeStamp - yyyymmddhhmmss (a time-format string), from
     the date: line of the mail
  moveBatch - a contentless id assigned to all the "move"
     objects that come in in the same piece of mail (for
     batching up multiple Rule Changes into a single proposal,
     etc)

Except for these three, and type (which is always "move"), the sender of the mail can control all the attributes of the move object. Syntactic sugar of various kinds will presumably be provided for common cases.

Rule attributes used by the engine:

  ruleOrder - rules are run in order of ruleOrder, lowest to highest.
  if - condition tested when a rule is run.
  then - verbs executed when an if is true; if this causes any
     change to the existing objects (creation, deletion,
     amendment), the ruleset is restarted.

Some verbs:

  delete(match) - deletes the matching object (with the lowest
     objectId, as usual)
  create(match) - creates a matching object
  set(match1)(match2) - Changes the object matching match1 so
     that it matches match2.
  send([email protected][....])(expression) - queues up the value
     of the expression for sending to [email protected] (or list thereof).
  sendObject([email protected][...])(match)(string) - queues up a
     print form of the matching object, with the string for a
     header, for sending.
  sendNow() - actually sends all queued stuff off.

Some functions for use in expressions: exists(match) count(match) timeGE(timevalue) - Tests if the current time is >= the given time. Used for timers. A possible engine optimization: duing each pass, keep track of the lowest-value timeGE that fails; set an actual timer to re-check all rules at that time, if nothing else has happened in the meantime.

Notes: - Some sort of "for" clause in rules may be needed for performance and clarity reasons. Otherwise, the only way to do something like sending the entire ruleset to a player in response to a query move would be to do passes through the first part of the ruleset, using some kludged-up bookkeeping attribute or object to keep track of which rules we haven't sent yet. Maybe that's OK? - Perhaps allow a rule to say that the engine shouldn't restart the ruleset after processing it? That, instead, it should just test the rule itself again, and then proceed onwards? - A more radical change to this model would be to have all object-changes take place in a second, "shadow", copy of the object-pool, and switch over to that object-pool only when no rule in the current pool wants to make any changes. Could be an engineControls switch? - Probably be good to have a setall(match1)(match2), that would efficiently set all objects matching match1 to match match2.

An Intial State

Loose conventions about ruleOrder:

  We will use ruleOrder 10000-19999 for rules that don't delete
  the object that sets them off (although they may set some
  attributes so that it doesn't set them off again).

  We will use ruleOrder 30000-39999 for rules that do delete, or
  otherwise make very significant changes (like changes of type)
  to the object that sets them off.

  We will use ruleOrder 99000-99999 for rules that should basically
  only run for cleanup-type reasons at the very end of an event.

Initial engine settings and (empty) player-list:

  objectId: 1
  type: engineSettings
  runType: rule

  objectId: 2
  type: playerList
  listContents: ""

Rules about registration:

 objectId: 3
 ruleOrder: 30010
 type: rule
 if: exists(type=="move" & subtype=="register" & moveSender==%s &
            nickname!="" & nickname==%n & moveTimeStamp==%tm & objectId==%mo) &
     !exists(type=="player" & nickname==%n) &
     exists(type=="playerList" & listContent==%pl)
 then: create(type=="player" & defaultEmail==%s & nickname==%n &
              registeredAt==%tm & score==0 & objectId==%po) &
       set(objectId==%mo)(processed==T & cleanup==T) &
       create(type=="newPlayer" & reference==%po & cleanup==T) &
       send(%pl,"New player" %n "registered at" %tm) &
       send(%s,"Welcome to Engine City," %n"!")
 comment: "if someone's registering, and e gave a nickname that
           isn't already in use, create a proxy object for em with
           their information, note that the move was processed for
           later cleanup, and create a newPlayer object for other
           rules to process.  Also tell everyone."

 objectId: 4
 ruleOrder: 30020
 type: rule
 if: exists(type=="move" & subtype=="register" & processed!=T &
            moveSender==%s & nickname!="" & nickname==%n & objectId==%mo) &
     exists(type=="player" & nickname==%n)
 then: sendObject(%s)(objectId==%mo)("A player" %n "is already registered") &
       delete(objectId==%mo)
 comment: "handle error case where someone tries to register with a
           name that's already in use."

 objectId: 5
 ruleOrder: 30030
 type: rule
 if: exists(type=="move" & subtype=="register" & moveSender==%s &
            nickname=="" & objectId==%mo) &
 then: sendObject(%s)(objectId==%mo)("You have to give a nickname when you
       register") & delete(objectId==%mo)
 comment: "handle error case where someone tries to register without
           a name."

 objectId: 6
 ruleOrder: 30040
 type: rule
 if: exists(type=="move" & from=="" & moveSender==%s & objectId==%o) &
     exists(type=="player" & defaultEmail==%s & nickname==%n)
 then: set(objectId==%o)(from==%n)
 comment: "if no from is given on a move, guess from the email address"

 objectId: 7
 ruleOrder: 30050
 type: rule
 if: exists(type=="move" & from==%f & moveSender==%s & objectId==%mo) &
     !exists(type=="player" & nickname==%f)
 then: sendObject(%s)(objectId==%mo)("You are no known player.") &
       delete(objectId==%mo)

 objectId: 8
 ruleOrder: 30060
 type: rule
 if: exists(type=="newPlayer" & listed!=T & defaultEmail==%e &
            objectId==%po) &
     exists(type=="playerList" & listContent==%l & objectId==%lo)
 then: set(objectId==%po)(listed==T) &
       set(objectId==%lo)(listContent==%l %e)
 comment: "Maintain a list of all player email addresses, for
           mass sendings"

A simple Win rule:

  objectId: 9
  ruleOrder: 10010
  type: rule
  if: exists(type=="player" & score>100 & nickname==%n & objectId==%o) &
      !exists(type=="win" & who==%o) &
      exists(type=="playerList" & listContent==%pl)
  then: create(type=="win" & who==%o) &
        send(%pl,%n "wins the game!")
  comment: "if a player has over 100 points, e wins"

  objectId: 10
  ruleOrder: 10020
  type: rule
  if: exists(type=="win")
  then: halt()
  comment: "the game ends when one or more players win"

Rules about Proposing:

  objectId: 11
  ruleOrder: 30120
  type: rule
  if: exists(type=="move" & subtype=="ruleChange" &
            ruleChangeType=="repeal" & target==%t &
            batchid==%b & objectId==%mo) &
      exists(objectId==%to & type=="rule")
  then: set(objectId==%mo)(type="changeBeingVotedOn")
  comment: "snarf in an incoming repeal rule change; we'll
     let ones that attempt to repeal a non-existing rule just
     fall through to the general error handler at first."

  objectId: 12
  ruleOrder: 30130
  type: rule
  if: exists(type=="move" & subtype=="ruleChange" &
            ruleChangeType=="amend" & target==%t &
            batchid==%b & objectId==%mo) &
      exists(objectId==%to & type=="rule")
  then: set(objectId==%mo)(type="changeBeingVotedOn")
  comment: "snarf in an incoming amendment rule change"

  objectId: 13
  ruleOrder: 30140
  type: rule
  if: exists(type=="move" & subtype=="ruleChange" &
            ruleChangeType=="create" & target==%t &
            batchid==%b & objectId==%mo)
  then: set(objectId==%mo)(type="changeBeingVotedOn")
  comment: "snarf in an incoming create rule change"

  objectId: 14
  ruleOrder: 30150
  type: rule
  if: exists(type=="changeBeingVotedOn" & sent!=T & batchId==%b &
            objectId==%o) &
      exists(type=="playerList" & listContent==%pl)
  then: sendObject(%pl,%o,"Proposed rule-change (proposal" %b"):") &
        set(objectId==%o)(sent==T)
  comment: "Tell all players about new proposals"

  objectId: 15
  ruleOrder: 30160
  type: rule
  if: exists(type="changeBeingVotedOn" & batchid==%b) &
      !exists(type=="voteInProgress" & propId==%b)
  then: create(type=="voteInProgress" & propId==%b) &
        expiryTime==timeNow()+10*24*60*60)
  comment: "Creates the voting object representing a pending Proposal"

Rules about Voting:

  objectId: 16
  ruleOrder: 30220
  type: rule
  if: exists(type=="move" & subtype=="vote" & propID==%p &
        from==%f & objectId==%o) &
      exists(type=="player" & nickname==%f) &
      exists(type="voteInProgress" & propId==%b) &
      !exists(type=="vote" & propID==%p & from==%f)
  then: set(objectId==%o)(type=="vote" & credited==F & cleanup==F)
  comment: "One Player, one unchangable vote per proposal"

  objectId: 17
  ruleOrder: 30230
  type: rule
  if: exists(type=="vote" & from==%f & credited==F & objectId==%vo) &
      exists(type=="player" & nickname==%f & score==%s & objectId==%po)
  then: set(objectId==%po)(score==1+%s) &
        set(objectId==%vo)(credited==T)
  comment: "One Point when you vote"

The End of the Voting Period:

  objectId: 18
  ruleOrder: 30320
  type: rule
  if: exists(type=="voteInProgress" & timeGE(expiryTime) &
        objectId==%o)
  then: set(%o)(type="voteCompleted")

  objectId: 19
  ruleOrder: 30330
  type: rule
  if: exists(type=="voteCompleted" & propId==%p) &
      exists(type=="vote" & propId==%p & from==%f & vote==%vc &
            noted!=T & objectId==%vo) &
      exists(type=="playerList" & listContent==%pl)
  then: set(objectId==%vo)(noted==T) &
        send(%pl,"Player" %f "receives one point for voting" %vc "on" %p)
  comment: "Announce content of votes"

Tallying Votes:

  objectId: 20
  ruleOrder: 30420
  type: rule
  if: exists(type=="voteCompleted" & objectId==%o & propId==%p &
            objectId==%o) &
      count(type=="vote" & propId==%p)<(count(type=="player")/4) &
      exists(type=="playerList" & listContent==%pl)
  then: set(objectId==%o)(type="proposalFailed" & cleanup==T) &
        send(%pl,"Proposal" %p "fails quorum.")

  objectId: 21
  ruleOrder: 30430
  type: rule
  if: exists(type=="voteCompleted" & objectId==%o & propId==%p &
            objectId==%o) &
      count(type=="vote" & propId==%p & vote=="FOR")>
      count(type=="vote" & propId==%p & vote=="AGAINST") &
      exists(type=="playerList" & listContent==%pl)
  then: set(objectId==%o)(type="proposalPassed" & credited==F) &
        send(%pl,"Proposal" %p "passes.")

  objectId: 22
  ruleOrder: 30440
  type: rule
  if: exists(type=="voteCompleted" & objectId==%o & propId==%p &
            objectId==%o) &
      count(type=="vote" & propId==%p & vote=="FOR")<=
      count(type=="vote" & propId==%p & vote=="AGAINST") &
      exists(type=="playerList" & listContent==%pl)
  then: set(objectId==%o)(type="proposalFailed" & cleanup==T) &
        send(%pl,"Proposal" %p "fails.")

  objectId: 23
  ruleOrder: 30450
  type: rule
  if: exists(type="proposalPassed" & from==%f & credited==F &
            objectId==%pro) &
      exists(type=="player" & nickname==%f & score==%s & objectId==%plo) &
      exists(type=="playerList" & listContent==%pl)
  then: set(objectId==%pro)(credited==T) &
        set(objectId==%plo)(score==5+%s) &
        send(%pl,%f "receives 5 points for the proposal ("5+%s")")
  comment: "Five points when your proposal passes."

Implementing passed rulechanges:

  objectId: 24
  ruleOrder: 30520
  type: rule
  if: exists(type="proposalPasssed" & propId==%b) &
      exists(type="changeBeingVotedOn" & batchId==%b & objectId==%o)
  then: set(objectId==%o)(type=="changePassed")

  objectId: 25
  ruleOrder: 30530
  type: rule
  if: exists(type=="changePassed" & ruleChangeType=="repeal" &
            target==%t & objectId==%o)
  then: delete(objectId==%t) &
        set(%o)(type=="changeDone" & cleanup==T)

  objectId: 26
  ruleOrder: 30540
  type: rule
  if: exists(type=="changePassed" & ruleChangeType=="amend" &
            target==%t & newif==%ni & newthen==%nt & neworder==%no &
            objectId==%o)
  then: set(objectId==%o)(if==%ni & then==%nt & ruleOrder==&no) &
        set(%o)(type=="changeDone" & cleanup==T)

  objectId: 27
  ruleOrder: 30550
  type: rule
  if: exists(type=="changePassed" & ruleChangeType=="create" &
            newif==%ni & newthen==%nt & neworder==%no &
            objectId==%o)
  then: create(type=="rule" & if==%ni & then==%nt & ruleOrder==&no) &
        set(%o)(type=="changeDone" & cleanup==T)

Cleanup Rules:

 objectId: 28
 ruleOrder: 99910
 type: rule
 if: exists(type=="proposalFailed" & propId==%p) &
     exists(type=="changeBeingVotedOn" & batchId==%p & objectId==%o)
 then: delete(objectId==%o)

 objectId: 29
 ruleOrder: 99920
 type: rule
 if: exists(type=="proposalFailed" & propId==%p) &
     exists(type=="vote" & batchId==%p & objectId==%o)
 then: delete(objecId==%o)

 objectId: 30
 ruleOrder: 99930
 type: rule
 if: exists(type=="proposalPassed" & propId==%p) &
     exists(type=="vote" & batchId==%p & objectId==%o)
 then: delete(objectId==%o)

 objectId: 31
 ruleOrder: 99940
 type: rule
 if: exists(type=="move" & processed!=T & objectId==%o &
            moveSender==%ms)
 then: sendObject(%ms)(objectId==%o)("This move didn't do anything") &
       delete(objectId==%o)
 comment: "If an unprocessed move is hanging around, delete it,
           and send a warning to the sender."

 objectId: 32
 ruleOrder: 99980
 type: rule
 if: exists(cleanup==T & objectId==%o)
 then: delete(objectId==%o)
 comment: "the Final Sweeper"

 objectId: 33
 ruleOrder: 99990
 type: rule
 if: T
 then: sendNow()
 comment: "Send out accumulated mailings"

Chat With Us

All three are connected so just choose your favorite!