Jump to content

Recommended Posts

Introduction

Ok, so I spent a couple days reverse engineering Utopia. I now know pretty much how it all works, and now you can find out too.

Here's some highlights, in case you don't want to wade through the code:

  • Forts:
    • Do protect a 1 unit radius against rebels
    • Will protect parked ships that have the same owner as the fort. Radius of 1 card.
    • Will protect both players ships when they're under active control. Radius of 1 card.
    • Do not otherwise contribute to your score
  • Bugs:
    • Scoring will overflow, if you have more than 65 crops + fishing boats combined.
    • It will also overflow if you earn more than 255 gold bars in a single turn. (Is that possible?)
    • The "float off bottom of screen" bug is likely due to island/status collision detection interacting poorly with EXEC's boundary detection. The game can "nudge" a boat across the boundary the EXEC checks for. There is a check for this, but it only has been applied properly to the left/right edges.
    • Delay loop in scoring display appear to want to update random number generator, but they don't actually.
  • Code quality:
    • Let's just say it's likely the priority was to have a compelling game, and have it out quickly, as opposed to writing the tightest, cleanest possible code.
  • Miscellaneous:
    • Pirates will never sail toward a parked PT boat on purpose. The "nudge" code, though, can nudge them through a tangle of PT boats.
    • Hurricanes are 5 times more destructive than tropical storms. All three forms of weather water crops at the same rate, though.
    • There's two copies of the "sinking fishing boat" animation in the ROM, due to how the EXEC distinguishes between background cards and MOB animations
    • Each island has precisely 29 squares.
    • When weather takes out something on your island, it will incur 0 to 101 casualties.

If you want more details, you'll have to dig through the code unless it's covered in the slightly more detailed review below. :-) Code attached.

Code Structure

The bulk of the main game code can be put into one of three categories -- The Timer Tick Task, Dispatches, Scoring Logic. The rest of the code is either initialization code, or support code for those three categories.

Timer Tick Task

The bulk of the game logic hangs off the the Timer Tick Task. This task runs 20 times a second, and it handles everything from spawning weather to collision detection to animating certain sinking ships. It's quite a lot of code and is fairly linear. The rough order of execution:

  • Update the weather, possibly creating new weather
  • Update the fish, possibly creating new schools of fish
  • Update the pirates, possibly spawning new pirates
  • Island vs. boat collision detection
  • Update the game clock
  • If not end of round / end of game:
    • Update the status line at bottom of screen
    • Update parked sinking ships
  • At end-of-round, fire off the scoring code and then show the scores
  • At end-of-game, show final score and halt

Dispatches

The next big set of code is the set of Dispatches. The EXEC mostly works by calling various functions in response to various events. Dispatches fall into a few categories:

  • Controller input dispatches. The action-button and disc handlers are pretty simple. The keypad dispatch is amazingly convoluted, but that makes sense when you consider everything that it has to account for.
  • Object vs. object dispatches. These handle pirates vs. PT boats and similar such things.
  • Object vs. land dispatches. This is what handles rain on crops, pirates sinking parked ships, or parked ships going fishing.

Scoring Logic

The final big piece of code is the scoring logic. Scoring happens in four phases: Income Computation, Population Update, Round Score Calculation, Rebellion.

  • Income Computation
    • During the round, every gold bar you earn (say due to fishing, rain on crops, etc.) gets tallied in this round's Gross Domestic Product (Round GDP), separate from your actual gold bar balance. That is, spending does not subtract from Round GDP even though it lowers your treasury balance.
    • At the end of the round, you get awarded additional gold as follows. Each of these contributes to the Round GDP except for the "baseline 10 bars."
      • 4 gold bars per factory
      • 1 gold bar per fishing boat
      • Productivity bonus: ((Schools + Hospitals) * Factories) + Hospitals, clamped to a maximum of 30 gold bars.
      • 10 gold bars of baseline income (does not contribute to the Round GDP).
  • Population computation -- expressed as a growth rate, resulting in exponential growth.
    • Fertility computation
      • Start with a baseline fertility rate of 5.0%
      • Increase fertility by 0.3% for every crop
      • Increase fertility by 0.3% for every hospital
      • Increase fertility by 0.1% for every house
      • Decrease fertility by 0.3% for every school
      • Clamp fertility to a minimum of 4.0%. You can't have fertility below 4% even if you filled the island with schools.
    • Mortality computation
      • Start with a baseline mortality rate of 1.1%
      • Decrease mortality by 0.3% for every hospital, but not below 0.2%. (This limit is applied before the next step.)
      • Increase mortality by 0.1% for every factory. If you fill the island with factories, your mortality rate will be 4.0%, matching the minimum allowed fertility.
    • New population: Population + Population * Fertility - Population * Mortality. Maximum allowed population is limited to 9999.
  • Round Score Calculation -- roughly, "approval rating", 0-100%
    • First compute the following four subscores:
      • Housing score: ((Houses * 500) / (Population / 100)) / 3. If larger than 30, clamp it to 30.
      • Per-capita GDP score: ((Round GDP * 100) / (Population / 100)) / 12. If larger than 30, clamp it at 30.
      • Food supply score: (((Fishing boats + Crops) * 500) / (Population / 100)) / 3. If this value is larger than 30, clamp it to 30.
        • Note: 65 * 500 = 32500. So, if you have more than 65 fishing boats + crops, this score can go negative. This is fixable by changing the BLE at $5B1E to a BNC, I think, so it treats the overflow case as a case that needs to clamp to 30.
      • General welfare score: 1 point for every school or hospital
    • Add up the four subscores, limiting the total to 100 or less. That's the per-round score.
  • Rebellion
    • Compare this round's score to the previous round, and consider it in absolute terms as well
    • If it dropped by more than 10 points or is below 30 points, add a rebel
    • If it increased by more than 10 points or is above 70 points, remove a rebel

If you want to see the exact details of how these pieces work, look at the code. For example, in the scoring section, some divides are rounding divides, and some are truncating divides. The population computation is actually carried out with scaled arithmetic (ie. fertility/mortality rates are multiplied by 10, and population divided by 10 when computing numbers of births and deaths.)

For anything else... see the code!

____

 

[Edited to fix some formatting issues in the nested bulleted lists.]

utopia.asm

Edited by intvnut
  • Like 5

Share this post


Link to post
Share on other sites

Joe, thank you so much for this! There are already some fascinating revelations, like the fact that schools decrease fertility. I'm very much looking forward to taking a browse through your disassembly; a lot of it will go over my head, but here's hoping some doesn't.

 

I assume this also puts to bed the notion that there's any hidden in-game content or "governor's award". I didn't think so, but a part of me held out hope. :)

Share this post


Link to post
Share on other sites

Joe, thank you so much for this! There are already some fascinating revelations, like the fact that schools decrease fertility. I'm very much looking forward to taking a browse through your disassembly; a lot of it will go over my head, but here's hoping some doesn't.

 

I assume this also puts to bed the notion that there's any hidden in-game content or "governor's award". I didn't think so, but a part of me held out hope. :)

 

Yep, the "Governor's Award" is just a fiction of the documentation, I suppose. If you make it to the end of the game with a reasonably successful island, you can award it to yourself. You know if you've succeeded or not, after all. ;-)

 

The only thing even remotely hidden in the ROM is a copyright notice, and a couple pieces of dead code.

Share this post


Link to post
Share on other sites

Fantastic, Joe!

 

Now, for the question on everybody's mind...

 

Is this just a prelude to working on Utopia 2, or a souped up version that runs at 60 Hz?

 

-dZ.

Share this post


Link to post
Share on other sites

Fantastic, Joe!

 

Now, for the question on everybody's mind...

 

Is this just a prelude to working on Utopia 2, or a souped up version that runs at 60 Hz?

 

-dZ.

 

Well, I can't answer for anyone else.

 

I can say this: I certainly am not working on either a 60Hz Utopia or a Utopia 2.

 

That said, if somebody does, I'd like to get a copy!

  • Like 1

Share this post


Link to post
Share on other sites

Fantastic, Joe!

 

Now, for the question on everybody's mind...

 

Is this just a prelude to working on Utopia 2, or a souped up version that runs at 60 Hz?

 

-dZ.

 

Well, I can't answer for anyone else.

 

I can say this: I certainly am not working on either a 60Hz Utopia or a Utopia 2.

 

That said, if somebody does, I'd like to get a copy!

 

Well, I was hoping. Perhaps that can be a test case when the new version of P-Machinery is ready? It certainly does not look as demanding (graphically), except for the back-end logic of the simulation--but you have de-mystified that. :)

 

I can imagine the various stages of the game and the event-driven nature of game-play lend themselves well to P-Machinery's abilities.

 

-dZ.

Share this post


Link to post
Share on other sites

My friend, you just earned yourself a spot in the credits section of the manual of the ColecoVision version. :D

 

Thank you SOOOOO much for doing this!

 

Utopia is a game that I want to code myself for the CV eventually, but it will take a couple of years, because I want to develop my BasicVision language and IDE first (in 2013). If it all works out, I'll code Utopia with BasicVision in 2014. Until then, I'll keep the data in the first post (as well as your disassembly file) in a safe spot in my records. :)

 

Thanks again!

  • Like 1

Share this post


Link to post
Share on other sites

Awesome! Thank you very, very much!

Share this post


Link to post
Share on other sites

My friend, you just earned yourself a spot in the credits section of the manual of the ColecoVision version. :D

 

Thank you SOOOOO much for doing this!

 

Utopia is a game that I want to code myself for the CV eventually, but it will take a couple of years, because I want to develop my BasicVision language and IDE first (in 2013). If it all works out, I'll code Utopia with BasicVision in 2014. Until then, I'll keep the data in the first post (as well as your disassembly file) in a safe spot in my records. :)

 

Thanks again!

 

Awesome! I love seeing my two favourite consoles come together! :)

Share this post


Link to post
Share on other sites

Fantastic, Joe!

 

Now, for the question on everybody's mind...

 

Is this just a prelude to working on Utopia 2, or a souped up version that runs at 60 Hz?

 

-dZ.

 

Well, I can't answer for anyone else.

 

I can say this: I certainly am not working on either a 60Hz Utopia or a Utopia 2.

 

That said, if somebody does, I'd like to get a copy!

 

 

I would be willing to do it if it can be sanctioned by Intellivision Productions, which I strongly doubt. I also would not recommend anyone else attempting a port without their permission.

  • Like 1

Share this post


Link to post
Share on other sites

In this question in another thread, dZ recently asked the following question, which was inspired by the Utopia graphics chart that had been displayed at ICHEG some time back.

 

 

Since you reversed-engineered the code, can you confirm that the outer edge is not in the map itself made invisible with the background colour? I know you disagreed before, but it seems plausible to me that they could mark some "special zone" that could be detected with hardware collision detection for the position of fish schools or pirate ships. It could also mark the range of the fort protection on your fishing ship.

 

The 1 card zone around the border of the islands does not contain special cards for hardware collision detection purposes, such as indicating the zone potentially protected by a fort.

 

Utopia uses a single method for determining whether a MOB or a card is in a 1 unit radius of a fort: Given a card position on the map, it looks through a 1 card radius of that position to see if the card contains a fort graphic. For MOBs, it first converts the MOB coordinate to a card position. The code which performs both checks looks like this:

.

        ;; ---------------------------------------------------------------- ;;
        ;;  Look for a fort within 1 square of a MOB.                       ;;
        ;; ---------------------------------------------------------------- ;;
FORT_NEAR_MOB:
L_564A: PSHR    R5                  ; 564A  
        PSHR    R1                  ; 564B  

        JSR     R5, MOB_TO_CARD     ; 564C  Convert MOB position to card posn

L_564F: CLRR    R0                  ; 564F  First search positive offsets

L_5650: SDBD                        ; 5650  \__ Point to neighbor offset table
        MVII    #NBR_TBL, R4        ; 5651  /

L_5654: [email protected]    R4,     R1          ; 5654  Get neighbor offset

        TSTR    R0                  ; 5655  \__ Add or subtract?
        BNEQ    L_565B              ; 5656  /

        ADDR    R2,     R1          ; 5658  \__ R0 = 0:  Add neighbor offset
        B       L_565F              ; 5659  /

L_565B: PSHR    R2                  ; 565B  \
        SUBR    R1,     R2          ; 565C   |_ R0 = 1:  Sub neighbor offset
        MOVR    R2,     R1          ; 565D   |
        PULR    R2                  ; 565E  /

L_565F: [email protected]    R1,     R3          ; 565F  Get card 
        SLR     R3,     2           ; 5660  \
        SLR     R3,     1           ; 5661   |_ Is it a fort?
        ANDI    #$00FF, R3          ; 5662   |
        CMPI    #$0001, R3          ; 5664  /
        BEQ     L_567A              ; 5666  

        SDBD                        ; 5668  \__ Are we at end of table?
        CMPI    #NBR_TBL+4, R4      ; 5669  /
        BEQ     L_5670              ; 566C  Yes:  Go around outer loop
        B       L_5654              ; 566E  No:   Get next offset.

L_5670: TSTR    R0                  ; 5670  \_ We did both pos and neg?
        BNEQ    L_5677              ; 5671  /  Yes:  Leave, no fort found.

        MVII    #$0001, R0          ; 5673  \_ Do negative offsets
        B       L_5650              ; 5675  /

L_5677: CLRR    R1                  ; 5677  \
        PULR    R2                  ; 5678   |- No fort found.  Leave.
        PULR    R7                  ; 5679  /

L_567A: [email protected]    R1,     R3          ; 567A  \
        SLLC    R3,     2           ; 567B   |- Whose fort is it?  CURPLR
        BOV     L_5686              ; 567C  /   or other player?

        MVII    #$0001, R3          ; 567E  Player 1's fort

L_5680: MVO     R3,     CURPLR      ; 5680  Remember which player's fort
        MVII    #$0001, R1          ; 5682  We found a fort!
        PULR    R2                  ; 5684  \_ return
        PULR    R7                  ; 5685  /

L_5686: CLRR    R3                  ; 5686  Player 0's fort
        B       L_5680              ; 5687  

        ; Offsets for looking around a square for a fort.
        ;   +1, +19, +20, +21 => E, SW, S, SE
        ;   -1, -19, -20, -21 => W, NE, N, NW
NBR_TBL:
        DECLE   $0001, $0013, $0014, $0015      ; 5689   0001 0013 0014 0015

        ;; ---------------------------------------------------------------- ;;
        ;;  Look for a fort within 1 square of a card.                      ;;
        ;;  Reuses code above for fort within 1 square of MOB.              ;;
        ;; ---------------------------------------------------------------- ;;
FORT_NEAR_CARD:
L_568D: PSHR    R5                  ; 568D  
        PSHR    R1                  ; 568E  
        B       L_564F              ; 568F  

.

As for the island graphics themselves, nothing special is stored in the cards around the border of the island. The islands are both represented as a list of card offsets:

.

        ;;  List of display offsets for the left and right islands,
        ;;  followed by GRAM card indices associated with these positions.
        ;;  (Ref: code @ 50D0 - 50F1, and subrtn at 511D - 5124)
        ;;
        ;;  $512F - $514B           $514C - $5168
        ;;  ....................    ....................
        ;;  ....................    ....................
        ;;  ..#.................    ..........##.#..#...
        ;;  .###................    ..........#######...
        ;;  .###................    ..........##..####..
        ;;  .#####..............    ...............####.
        ;;  ...####.............    ................###.
        ;;  ....#####...........    ...............####.
        ;;  ...###.#####........    .................#..
        ;;  ....................    ....................
        ;;  ....................    ....................
        ;;  ....................    ....................
LFT_ISLE_OFS_TBL:
        DECLE   $002A,  $003D,  $003E,  $003F   ; 512F   002A 003D 003E 003F
        DECLE   $0051,  $0052,  $0053,  $0065   ; 5133   0051 0052 0053 0065
        DECLE   $0066,  $0067,  $0068,  $0069   ; 5137   0066 0067 0068 0069
        DECLE   $007B,  $007C,  $007D,  $007E   ; 513B   007B 007C 007D 007E
        DECLE   $0090,  $0091,  $0092,  $0093   ; 513F   0090 0091 0092 0093
        DECLE   $0094,  $00A3,  $00A4,  $00A5   ; 5143   0094 00A3 00A4 00A5
        DECLE   $00A7,  $00A8,  $00A9,  $00AA   ; 5147   00A7 00A8 00A9 00AA
        DECLE   $00AB                           ; 514B   00AB

RGT_ISLE_OFS_TBL:
        DECLE   $0032,  $0033,  $0035,  $0038   ; 514C   0032 0033 0035 0038
        DECLE   $0046,  $0047,  $0048,  $0049   ; 5150   0046 0047 0048 0049
        DECLE   $004A,  $004B,  $004C,  $005A   ; 5154   004A 004B 004C 005A
        DECLE   $005B,  $005E,  $005F,  $0060   ; 5158   005B 005E 005F 0060
        DECLE   $0061,  $0073,  $0074,  $0075   ; 515C   0061 0073 0074 0075
        DECLE   $0076,  $0088,  $0089,  $008A   ; 5160   0076 0088 0089 008A
        DECLE   $009B,  $009C,  $009D,  $009E   ; 5164   009B 009C 009D 009E
        DECLE   $00B1                           ; 5168   00B1 

LFT_ISLE_PIC_TBL:
        DECLE           $0013,  $0014,  $0000   ; 5169        0013 0014 0000
        DECLE   $0015,  $0017,  $0000,  $0018   ; 516C   0015 0017 0000 0018
        DECLE   $001D,  $001E,  $0000,  $0020   ; 5170   001D 001E 0000 0020 
        DECLE   $0015,  $001D,  $0000,  $0000   ; 5174   0015 001D 0000 0000
        DECLE   $0015,  $0017,  $0000,  $001E   ; 5178   0015 0017 0000 001E
        DECLE   $0020,  $0015,  $0019,  $001E   ; 517C   0020 0015 0019 001E
        DECLE   $001F,  $001D,  $001E,  $001C   ; 5180   001F 001D 001E 001C
        DECLE   $001B,  $001A                   ; 5184   001B 001A          

RGT_ISLE_PIC_TBL:
        DECLE                   $0014,  $0015   ; 5186             0014 0015
        DECLE   $0013,  $0013,  $0017,  $0000   ; 5188   0013 0013 0017 0000
        DECLE   $001B,  $001E,  $001C,  $0020   ; 518C   001B 001E 001C 0020
        DECLE   $0018,  $001D,  $001F,  $001D   ; 5190   0018 001D 001F 001D
        DECLE   $0000,  $0000,  $0015,  $001D   ; 5194   0000 0000 0015 001D
        DECLE   $0000,  $0000,  $0015,  $0017   ; 5198   0000 0000 0015 0017
        DECLE   $0000,  $0018,  $0019,  $001E   ; 519C   0000 0018 0019 001E
        DECLE   $0000,  $001F,  $0016           ; 51A0   0000 001F 0016

.

No corresponding table exists for the boundary of either island.

 

And finally, if you examine BACKTAB during the game, you can see that there is nothing special about the water cards outside the islands. If there were, then there would be non-zero BACKTAB entries in the leftmost and rightmost columns in particular. Rather, they're all zeros. If you look more closely, the only non-zero BACKTAB cards in the main map exactly correspond to the two sets of tables above.

.

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 489B 0000 0000 0000 0000 0000 0000 0000 08A3 08AB 0000 089B 0000 0000 089B 0000 0000 0000 
0000 48A3 4803 48AB 0000 0000 0000 0000 0000 0000 08BB 0803 08DB 08F3 08E3 0903 08C3 0000 0000 0000
0000 48BB 4803 48C3 0000 0000 0000 0000 0000 0000 08EB 08FB 0000 0000 08EB 0803 0803 08AB 0000 0000 
0000 48EB 48F3 4803 4903 48AB 0000 0000 0000 0000 0000 0000 0000 0000 0000 08EB 0803 0803 08AB 0000
0000 0000 0000 48EB 4803 4803 48AB 0000 0000 0000 0000 0000 0000 0000 0000 0000 08BB 0803 08C3 0000 
0000 0000 0000 0000 48BB 4803 48F3 4903 48AB 0000 0000 0000 0000 0000 0000 08CB 08F3 0803 08FB 0000
0000 0000 0000 48CB 48F3 48FB 0000 48EB 48F3 48E3 48DB 48D3 0000 0000 0000 0000 0000 08B3 0000 0000 
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 
0000 0004 008C 0084 0084 0000 0000 00AE 0086 0006 008E 008E 00BE 0000 0000 0002 008A 0082 0082 0000

.

  • Like 1

Share this post


Link to post
Share on other sites

I'm waiting for Dystopia.

  • Like 1

Share this post


Link to post
Share on other sites

Awesome thread and thanks very much for the insights on the calculations! 

 

I'll need to do some more experiments first, but doesn't seem like there is any minimum fertility rate, or at least it's not working as designed. With 5 factories and 24 schools, I was getting a steady -4.6% population loss every turn for the part I kept track of.

 

Great to know that schools are more effective at population control than factories! 

 

Curious to know if there is anything in the code that would explain getting 100 scores a while after population goes negative. When it first goes negative, you lose all population-based sources of score and just get the schools+hospitals flat score, but after a few turns of 'adjustment period' where everyone gets used to being dead, scores jump to a consistent 100 every round.  I tried it out today just to make sure my recollection was accurate and was able to reproduce it readily enough. 

 

It's not a viable highscore strategy or anything, but my friends and I used to play that way BITD as an amusing alternative. Endless fun rooting for storms to hit you, etc. 🙂

Share this post


Link to post
Share on other sites
2 hours ago, BiffMan said:

Awesome thread and thanks very much for the insights on the calculations! 

 

I'll need to do some more experiments first, but doesn't seem like there is any minimum fertility rate, or at least it's not working as designed. With 5 factories and 24 schools, I was getting a steady -4.6% population loss every turn for the part I kept track of.

 

Great to know that schools are more effective at population control than factories! 

 

Curious to know if there is anything in the code that would explain getting 100 scores a while after population goes negative. When it first goes negative, you lose all population-based sources of score and just get the schools+hospitals flat score, but after a few turns of 'adjustment period' where everyone gets used to being dead, scores jump to a consistent 100 every round.  I tried it out today just to make sure my recollection was accurate and was able to reproduce it readily enough. 

 

It's not a viable highscore strategy or anything, but my friends and I used to play that way BITD as an amusing alternative. Endless fun rooting for storms to hit you, etc. 🙂

 

Hmm.  Let me go take a look at the source.  I probably made some errors.  For example:

        MVII    #$0028, R3          ; 5A40  Fertility baseline of 50 (5.0%)

$28 is 40, not 50.  So... clearly I need to review this.

 

Share this post


Link to post
Share on other sites
2 hours ago, BiffMan said:

Awesome thread and thanks very much for the insights on the calculations! 

 

I'll need to do some more experiments first, but doesn't seem like there is any minimum fertility rate, or at least it's not working as designed. With 5 factories and 24 schools, I was getting a steady -4.6% population loss every turn for the part I kept track of.

OK, I looked at the code, and I apparently must have made a gigantic brain fart in the wee hours of the morning when I said there was a lower bound of 4% on the fertility.

 

        CMPI    #$0040, R3          ; 5A6C  \
        BLE     L_5A72              ; 5A6E   |- Min fertility of 40.
        MVII    #$0040, R3          ; 5A70  /

L_5A72: MVI     G_0324, R0          ; 5A72  Get pop/10

That comment is 100% bogus.

 

Here's what it should say:

        CMPI    #$0040, R3          ; 5A6C  \
        BLE     L_5A72              ; 5A6E   |- Max fertility of 64.
        MVII    #$0040, R3          ; 5A70  /

L_5A72: MVI     G_0324, R0          ; 5A72  Get pop/10

That's right, it's a clamp on the upper bound of fertility, not the lower bound. 

 

*d'oh*

 

24 schools should be a -7.2% on bias on fertility.  4.0% - 7.2% = -3.2%.  5 factories should be 1.1% + 5 * 0.1% = 1.6% mortality. 

 

So, for a population of 1000, you should see 1000 - 3.2% *1000 - 1.6% * 1000 = 1000 - 32 - 16 = 952.

 

A -4.6% would take 1000 down to 954.

 

Can you double check your measurements?

 

As for negative population driving 100 scores, I can see that.   I need to investigate what the divide routine does when passed a negative divisor and/or dividend.  It's possible one or more steps treat the negative number as a large positive number, giving an off-the-charts score.   There are three score components that involve population, and those only add to 90.  You'd need to get the other 10 points through schools or hospitals (general welfare score).

  • Like 1

Share this post


Link to post
Share on other sites
21 hours ago, intvnut said:

OK, I looked at the code, and I apparently must have made a gigantic brain fart in the wee hours of the morning when I said there was a lower bound of 4% on the fertility. 

 

Can you double check your measurements?

 

As for negative population driving 100 scores, I can see that.   I need to investigate what the divide routine does when passed a negative divisor and/or dividend.  It's possible one or more steps treat the negative number as a large positive number, giving an off-the-charts score.   There are three score components that involve population, and those only add to 90.  You'd need to get the other 10 points through schools or hospitals (general welfare score).

Played again today with my daughter and confirmed the -.3% per school and -.1% per factory do work as advertised. Therefore building schools in the historical storm path is by far the most cost effective means of lowering population. Also means that 29 schools and a -5.8% (4% - 8.7% - 1.1%) population rate is the max.

 

I did stumble onto a discovery in regards to the scoring glitch at low populations, which I'd love to know the coding behind... The score tanking happens exactly with a population -100<X<100 at which point only schools and hospitals count toward score, all population based ones give zero. Above 100 pop is normal scoring and below -100 pop gives max 100 score. Unknown if the clip is 99 or 100, but my daughter caught it going in and out and even managed to go to -101 for max score then to -99 the next turn for 19 points and a rebel.  Definitely a threshold thing. 

 

Thanks again for all this great info, it's delightful to know schools are more effective than factories for growth suppression, fishing boats don't affect growth rate, and houses only increase by 1/3 the reduction from a school. 

 

Still left to the whims of the weather, but I think a min/max strategy of 2 fishing boats, 2 houses, 3-4 factories, and a crap ton of schools is quite viable. You'll score well in the short term and if you can get lucky and not linger in the 100 to -100 range too long, there's no significant downside to going negative. 

 

Fun! 🙂

Share this post


Link to post
Share on other sites
Posted (edited)

I will definitely have to review my disassembly and make corrections.

 

I'm not sure I followed this calculation:

1 hour ago, BiffMan said:

Also means that 29 schools and a -5.8% (4% - 8.7% - 1.1%) population rate is the max.

Where did the 1.1% come from?

 

EDIT:  Never mind:  Baseline mortality.

Edited by intvnut
Realized my error.

Share this post


Link to post
Share on other sites

 

2 hours ago, BiffMan said:

The score tanking happens exactly with a population -100<X<100 at which point only schools and hospitals count toward score, all population based ones give zero. Above 100 pop is normal scoring and below -100 pop gives max 100 score.

OK, I believe -100 and below should give you "max 100".  Here's my reasoning:

 

Population is stored as a 16-bit value.  When computing the score, the code performs a non-rounding divide by 100:

L_5ABD: MVII    #POPU_0,R4          ; 5ABD  \
        JSR     R5,     L_5A29      ; 5ABF   |_ Get player's (updated)
        SDBD                        ; 5AC2   |  population count
        [email protected]    R4,     R1          ; 5AC3  /

        MVII    #$0064, R2          ; 5AC4  \_ Pop / 100
        JSR     R5,     X_DIV       ; 5AC6  /

I checked the divide code, and it appears to correctly account for the signs of both numerator and denominator.  It remembers the sign of the numerator and denominator, flips them both positive, and then sets the sign of the result to the XOR of the two signs (effectively).  So +/+ gives +, -/- gives +, and +/- or -/+ gives -.

 

So, the divide above will clamp populations from -99 to 99 down to 0 for scoring, fitting your -100 < X < 100 criterion.

 

For -100 and below, the computation above results in a negative value.  The subsequent subscore computations use this negative value and compute negative scores.  For example, this computes a negative housing score:

 

        MVII    #HOUS_0,R1          ; 5ACB  \
        ADD     CURPLR, R1          ; 5ACD   |- Get number of houses
        [email protected]    R1,     R0          ; 5ACF  /

        MVII    #$01F4, R1          ; 5AD0  \_ Houses * 500
        JSR     R5,     X_MPY       ; 5AD2  /

        MOVR    R2,     R1          ; 5AD5  \
        MVI     G_0324, R2          ; 5AD6   |- (Houses * 500) / (pop / 100)
        JSR     R5,     X_DIVR      ; 5AD8  /   rounded

        MOVR    R0,     R1          ; 5ADB  \
        MVII    #$0003, R2          ; 5ADC   |- ((Houses*500)/(pop/100)) / 3
        JSR     R5,     X_DIVR      ; 5ADE  /   rounded

        CMPI    #$001E, R0          ; 5AE1  \
        BLE     L_5AE7              ; 5AE3   |- Clamp housing score at 30
        MVII    #$001E, R0          ; 5AE5  /

So what happens next?

L_5AE7: MVO     R0,     H_SCOR      ; 5AE7  Remember housing score 

That looks pretty innocuous, until you realize H_SCOR is in 8-bit memory.  That negative score now looks like a large positive  8-bit value.

 

This pattern repeats for the other subscores.

        MVII    #RGLD_0,R4          ; 5AE9  \
        ADD     CURPLR, R4          ; 5AEB   |- Get gold earned this round
        [email protected]    R4,     R0          ; 5AED  /   This is total GDP for round

        MVII    #$0064, R1          ; 5AEE  \_ Multiply by 100
        JSR     R5,     X_MPY       ; 5AF0  /

        MOVR    R2,     R1          ; 5AF3  \   Compute per capita GDP as 
        MVI     G_0324, R2          ; 5AF4   |- (gold * 100) / (pop / 100)
        JSR     R5,     X_DIVR      ; 5AF6  /   rounded

        MOVR    R0,     R1          ; 5AF9  \   Per capita GDP score is 
        MVII    #$000C, R2          ; 5AFA   |- ((gold*100) / (pop/100)) / 12
        JSR     R5,     X_DIVR      ; 5AFC  /   rounded

        CMPI    #$001E, R0          ; 5AFF  \
        BLE     L_5B05              ; 5B01   |- Clamp GDP score at 30
        MVII    #$001E, R0          ; 5B03  /

L_5B05: MVO     R0,     G_SCOR      ; 5B05  Remember per capita GDP score

G_SCOR is also in 8-bit memory.  This will also look like a large positive 8-bit value.

 

        ;;  Compute food-supply score
        ;;  Note:  This can overflow if (crops + fishing boats) > 65
        MVII    #F_BO_0,R1          ; 5B07  \
        ADD     CURPLR, R1          ; 5B09   |- Get number of fishing boats
        [email protected]    R1,     R0          ; 5B0B  /

        MVII    #CROP_0,R2          ; 5B0C  \
        ADD     CURPLR, R2          ; 5B0E   |_ Add number of crops
        [email protected]    R2,     R1          ; 5B10   |
        ADDR    R0,     R1          ; 5B11  /

        MVII    #$01F4, R0          ; 5B12  \_ (boats + crops) * 500
        JSR     R5,     X_MPY       ; 5B14  / > Can go "negative" if more
                                    ;         > than 65 fishing boats + crops

        MOVR    R2,     R1          ; 5B17  \
        MVI     G_0324, R2          ; 5B18   |- (boat+crops)*500 / (pop/100)
        JSR     R5,     X_DIVR      ; 5B1A  /

        MOVR    R0,     R1          ; 5B1D  \   Food supply score:
        MVII    #$0003, R2          ; 5B1E   |- ((boat+crops)*500/(pop/100))/3
        JSR     R5,     X_DIVR      ; 5B20  /

        CMPI    #$001E, R0          ; 5B23  \
        BLE     L_5B29              ; 5B25   |- Clamp at 30
        MVII    #$001E, R0          ; 5B27  /

L_5B29: MVO     R0,     F_SCOR      ; 5B29  Remember food score

Ditto F_SCOR.

 

Finally, they all get added together with the general welfare score as 16-bit arithmetic, and clamped to 100.  Because the negative values above got whitewashed into looking like positive values, this sum is way larger than 100 but positive:

        CLRR    R1                  ; 5B2B  \   Base round score:
        ADD     H_SCOR, R1          ; 5B2C   |_ housing + GDP + food
        ADD     G_SCOR, R1          ; 5B2E   |
        ADD     F_SCOR, R1          ; 5B30  /

        ;; General welfare scoring:
        MVII    #SCHL_0,R2          ; 5B32  \
        ADD     CURPLR, R2          ; 5B34   |_ Add 1 point for every school
        [email protected]    R2,     R0          ; 5B36   |
        ADDR    R0,     R1          ; 5B37  /
        MVII    #HOSP_0,R2          ; 5B38  \
        ADD     CURPLR, R2          ; 5B3A   |_ Add 1 point for every hospital
        [email protected]    R2,     R0          ; 5B3C   |
        ADDR    R0,     R1          ; 5B3D  /

        CMPI    #$0064, R1          ; 5B3E  \
        BLE     L_5B44              ; 5B40   |- Clamp round score at 100
        MVII    #$0064, R1          ; 5B42  /

 

Mystery explained?

  • Thanks 2

Share this post


Link to post
Share on other sites
8 hours ago, intvnut said:

Mystery explained?

Beautifully, thank you!!  That was awesome stuff and great to see how something so simple as passing a value from 16-bit to 8-bit memory can result in amusing secondary outcomes.  Also really appreciate the confirmation on the -99 to 99 rounding explaining the score bomb-out.  BITD, we used to think it was a matter of time before scores went to 100, but I much prefer a math-based explanation!  😁

 

The other fun bit this explains is why when you do go negative on population, you start seeing a slight increase in population (say from -110 to -106) each round if you are school-heavy.  Since the big factor there is the negative fertility, not the mortality rate, and it's adding the fertility, you'd expect a negative pop plus a negative fertility, not a negative pop minus a positive mortality.

 

I have to say, all your digging into the code makes me all the more impressed with Don Daglow's accomplishments.  For the era, that's an incredible number of under-the-hood calculations going on to produce a very organic result.  While it's fun to find the edges of the envelope where the calculations and 8-bit memory cause issues, that takes nothing away from how overall solid the game is!

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, BiffMan said:

Also really appreciate the confirmation on the -99 to 99 rounding explaining the score bomb-out.

I forgot one other detail:  The divide routine returns 0 if you divide by 0. 

 

Since the code divides population by 100, rounding toward 0, this gives you a 0 divisor for a bunch of the calculations.  Dividing by 0 population zeroes out those scores.  That fleshes out the rest of the explanation.

  • Thanks 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...