[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Apple Games Disassembly Project - Ultima 1



Holy mother of God...  Thank you!!!

I'll get another JACE cheat up for this one as soon as I get the rest of the mockingboard kinks out.

-B

On Thursday, May 10, 2012 2:48:54 PM UTC-5, Michael wrote:
> Apple Games Disassembly Project - Ultima 1
> 
> Curious how Ultima 1 rendering works?
> Curious why the original Ultima 1 runs like crap?
>  (That's a technical term for 'slow'. =)
> Curious about learning how to optimize a 6502 map renderer?
> 
> Since I couldn't find any *good* technical notes
> about the original Ultima 1 for the Apple ...
> 
> ... for your viewing pleasure:
> 
> Michael's Ultima 1 Annotated Technical Notes
> 
> 
> Table of Contents:
>  0. Required Materials
>  1. View outdoor tiles
>  2. Map Viewer
>  3. Fast Map Drawing
>  4. Annotated original DrawMap
>  5. Monster Shapes
>  6. Bootstrapping
>  7. Applesoft Variables
>  8. Bugs
>  9. Misc.
> 
> 
> 0. Required Materials
> = = = = = = = = = =
>  (x) Ultima 1 (Original 1981 DOS3.3 version) disks/images
>      Notes:
>        I mounted drive 1 with: ultima_1.dsk.gz
>        I mounted drive 2 with: ultima_2.dsk.gz
>      from Asimov: /games/rpg/ultima/ultima_I/
>        After unzipping if they don't have the '.dsk'
>      extension rename them so they do.
>  (x) Apple ][ (real or emulator)
> 
> You can tell which version you have by the border
>  No map border = original 1981
>  Blue map border = re-released 1986 remake
> 
> 
> 1. View outdoor tiles
> = = = = = = = = = = =
> 
>  1. Boot Ultima 1
>  2. At the ] prompt press CTRL-RESET twice
>     (AppleWin: Ctrl-F2)
>  3. Type
>     (or copy and use Shift-Insert to paste into AppleWin)
> BLOAD ULTSHAPES,A$800
> BLOAD DRAW 64.OBJ,A$A00
> CALL-151
> 6:9 5 80
> 1000:00 10 20 30 40 50 60 70
> 1040:80 90 A0 B0 C0 D0 E0 F0
> F3E2G
> C052
> A00G
> 
> To get back to Applesoft
> C053
> <Ctr-C> RETURN
> TEXT
> 
> Tiles: (in hex)
>  00 Water
>  10 Land
>  20 Woods
>  30 Mountains
>  40 Castle (top-down)
>  50 Sign
>  60 Town/City
>  70 Dungeon
>  80 Fighter (and Avatar)
>  90 Horse
>  A0 Cart
>  B0 Raft
>  C0 Pirate Ship
>  D0 Hover Car
>  E0 Space Shuttle
>  F0 Time Machine
> 
> To draw the map from Applesoft:
>  CALL 2560
> 
> 
> 2. More Fun - Map Viewer
> = = = = = = = = = = = =
> 
> First, load the necessary data and code...
> BLOAD ULTSHAPES,D1,A$800
> BLOAD DRAW 64.OBJ,D1,A$A00
> BLOAD BTERRA0,D2,A$1000
> 
> Then enter this program to view the world =)
> 
> CALL-151
> - - - - - 8< - - - - -
> C00:20 E2 F3 A9 81 85 08 85
> C08:EB D0 20 AD 8B 0A C9 0A
> C10:D0 04 A9 0C D0 06 C9 0C
> C18:D0 05 A9 0A 8D 8B 0A A5
> C20:EB 49 01 85 EB AA 9D D2
> C28:BF D0 06 A9 20 85 06 85
> C30:07 20 00 0A A5 06 20 DA
> C38:FD A9 A0 20 ED FD A5 07
> C40:20 DA FD A9 A0 20 ED FD
> C48:AE C5 0C E8 8A 20 ED FD
> C50:20 8E FD AD 00 C0 10 FB
> C58:8D 10 C0 C9 8D F0 47 C9
> C60:8B F0 43 C9 AF F0 3B C9
> C68:8A F0 37 C9 88 F0 2E C9
> C70:95 F0 26 C9 A0 F0 B2 C9
> C78:9B F0 8E C9 D1 F0 2C C9
> C80:B1 90 D0 C9 B5 B0 CC AA
> C88:CA 8E C5 0C A2 00 BD B8
> C90:0C F0 0C 20 BD 9E E8 D0
> C98:F5 E6 06 E6 06 C6 06 18
> CA0:90 8F E6 07 E6 07 C6 07
> CA8:18 90 F5 A0 03 A9 00 C6
> CB0:B8 91 B8 88 10 FB 60 EA
> CB8:84 C2 CC CF C1 C4 A0 C2
> CC0:D4 C5 D2 D2 C1 BE AC C1
> CC8:A4 B1 B0 B0 B0 8D 00
> - - - - - 8< - - - - -
> C00G
> 
> Map Viewer Keys
>  RETURN Move Up (or Up arrow)
>  /      Move Down (or Down arrow)
>  <-     Move Left
>  ->     Move Right
>  Space  Teleport to center of map
>  1      Load map 0 (Lands of Lord British)
>  2      Load map 1 (Lands of the Feudal Lords)
>  3      Load map 2 (Lands of the Dark Unknown)
>  4      Load map 3 (Lands of Danger and Despair)
>  ESC    Toggle fullscreen
>  Q      Exit
> 
> Note: Prints X Y W, where W is the map/world last loaded
> 
> For map details:
>  http://shrines.rpgclassics.com/pc/ultima1/overworld.shtml
> 
> Annoted Ultima 1 (original) MapViewer 1e
> 
>  C00:20 E2 F3        JSR HGR
>  C03:A9 81           LDA #81     ; sprite #80 = Fighter (vehicle =
> none), #1 = MixedMode
>  C05:85 08           STA $8      ; player vehicle
>  C07:85 EB           STA $EB     ; FullScreenFlag
>  C09:D0 20           BNE .warp
>  C0B:AD 8B 0A TFS    LDA $0A8B   ; have custom DrawMap Patch
> v4?
>  C0E:C9 0A           CMP #A
>  C10:D0 04           BNE .mix
>  C12:A9 0C           LDA #C
>  C14:D0 06           BNE .doFS
>  C16:C9 0C    .mix   CMP #C
>  C18:D0 05           BNE .tfs
>  C1A:A9 0A           LDA #A
>  C1C:8D 8B 0A .doFS  STA $0A8B
>  C1F:A5 EB    .tfs   LDA $EB     ; ToggleFullScreen
>  C21:49 01           EOR #1
>  C23:85 EB    .fs    STA $EB     ; FullScreen
>  C25:AA              TAX
>  C26:9D D2 BF        STA $BFD2,X ; $BFD2+$80 = $C052 or $C053
>  C29:D0 06           BNE .draw   ; don't reset player position
>  C2B:A9 20    .warp  LDA #20     ; reset player to center of map
>  C2D:85 06           STA PlayerX
>  C2F:85 07           STA PlayerY
>  C31:20 00 0A .draw  JSR DrawMap
>  C34:A5 06           LDA $06
>  C36:20 DA FD        JSR PRBYTE  ; $FDDA
>  C39:A9 A0           LDA ' '
>  C3B:20 ED FD        JSR COUT    ; $FDED
>  C3D:A5 07           LDA $07
>  C40:20 DA FD        JSR PRBTE   ; $FDDA
>  C43:A9 A0           LDA ' '
>  C45:20 ED FD        JSR COUT    ; $FDED
>  C48:AE C5 0C        LDX LOAD+#D ; last loaded map, bterra#
>  C4B:E8              INX
>  C4C:8A              TXA
>  C4D:20 ED FD        JSR COUT    ; $FDED
>  C50:20 8E FD        JSR CROUT   ; $FD8E -> $FDED
>  C53:AD 00 C0 .key   LDA Key
>  C56:10 FB           BPL .key
>  C58:8D 10 C0        STA KeyStrobe
>  C5B:C9 8D           CMP kRETURN ; #$8D Return
>  C5D:F0 47           BEQ .up
>  C5F:C9 8B           CMP kUP     ; #$8B Ctrl-K ^
>  C61:F0 43           BEQ .up     :             |
>  C63:C9 AF           CMP kSLASH  ; #$AF /
>  C65:F0 3B           BEQ .dn     ;             |
>  C67:C9 8A           CMP kDOWN   ; #$8A Ctrl-J v
>  C69:F0 37           BEQ .dn
>  C6B:C9 88           CMP kLEFT   ; #$88 Ctrl-H <--
>  C6D:F0 2E           BEQ .lEFT
>  C6F:C9 95           CMP kRIGHT  ; #$95 Ctrl-U -->
>  C71:F0 26           BEQ .rIGHT
>  C73:C9 A0           CMP kSPACE  ; #$A0 ' '
>  C75:F0 B2           BEQ .pos
>  C77:C9 9B           CMP kESC    ; #$9B ESC
>  C79:F0 8E           BEQ TFS     ; ToggleFullScreen
>  C7B:C9 D1           CMP 'Q'     ; #$D1 'Q'
>  C7D:F0 2C           BEQ .done
>  C7F:C9 B1           CMP '1'     ; <  '1'
>  C81:90 D0           BCC .key
>  C83:C9 B5           CMP '5'     ; >= '5'
>  C85:B0 CC           BCS .key
>  C87:AA              TAX
>  C88:CA              DEX
>  C89:8E C5 0C        STX LOAD+#D ; bterra#
>  C8C:A2 00           LDX #0
>  C8E:BD B8 0C .load  LDA $0CC0,X
>  C91:F0 0C           BEQ .2draw  ; to lazy to calculate .draw offset
>  C93:20 BD 9E        JSR $9EBD   ; DOS3.3 -> $9EBD CSW
>  C96:E8              INX
>  C97:D0 F5           BNE load
>  C99:E6 06    .right INC PlayerX
>  C9B:E6 06           INC PlayerX
>  C9D:C6 06    .left  DEC PlayerX
>  C9F:18       .2draw CLC
>  CA0:90 8F           BCC .draw
>  CA2:E6 07    .dn    INC PlayerY
>  CA4:E6 07           INC PlayerY
>  CA6:C6 07    .up    DEC PlayerY
>  CA8:18              CLC
>  CA9:90 F5           BCC .2draw+1 ; to lazy to calculate .draw offset
>  CAB:A0 03    .done  LDY #3       ; clear rest of input line (may have
> had a BLOAD)
>  CAD:A9 00           LDA #0       ; Applesoft: D7EB: LDY #2 LDA
> ($B8),Y --> if line #0 then END
>  CAF:C6 B8           DEC $B8      ; Monitor: get 'CR' ($8D) from
> 'C00G'
>  CB1:91 B8    .clear STA ($B8),Y  ;    w/o adjust, would get 'A' from
> BLOAD...
>  CB3:88              DEY          ; we can't store '$8D' as Applesoft
> would assume 'PLOT' token
>  CB4:10 FB           BPl .clear   ; $0CB3
>  CB6:60
>  CB7:EA              NOP          ; pad out to $CB8
>  CB8:84       LOAD   DB $84   ; Ctrl-D for DOS3.3
>  CB9:C2 CC CF C1 C4  ASC "BLOAD BTERRA>,A$1000" ; $BE '>' + 1 --> '?'
> $BF
>     A0 C2 D4 C5 D2
>     D2 C1 BE AC C1
>     A4 B1 B0 B0 B0
> CCD:8D 00           DB $8D, $8D, $00
> 
> AppleWin Symbols
>  DB LOAD CB8:CCE
> 
>  SYM MapView = C00
>  SYM .mix    = C16
>  SYM .doFS   = C1C
>  SYM .tfs    = C1F
>  SYM .fs     = C2D
>  SYM .warp   = C2B
>  SYM .draw   = C31
>  SYM .key    = C53
>  SYM .load   = C8E
>  SYM .right  = C99
>  SYM .left   = C9D
>  SYM .2draw  = C9F
>  SYM .down   = CA2
>  SYM .up     = CA6
>  SYM .done   = CAB
>  SYM .clear  = CB1
> 
> 
> 3. Fast Map Drawing
> = = = = = = = = = =
> 
> You can definitely tell Richard Garriot was
> still cutting his teeth learning 6502 assembly.
> 
> Ultima 1 outdoor drawing is SLOW because:
> 
> - it *always* draw an extra 2 bottom rows of the outside map
>   even though those are always covered by
>   the 4 line command history and status window!
> - it draws the 14x16 sprites in 2 column passes;
>   redundant HGR Y calculations at $A6E
> - The GetMapPtr at $A31 uses a SLOW Y*64 calculation
> - it doesn't use a HGR Y table
> - The map tiles are organized by column, not scanline
>   This means the blitter is horribly inefficient.
>   (The enhanced version of Ultima 1 uses a scanline blitter! =)
> - It always draws the map tile under the avatar and THEN
>   draws the vehicle instead of ONLY drawing the vehicle
>   for the player tile. This causes flicker when moving.
> 
> Here is a faster and smaller version of DrawMap.
>  - About tTwice as fast as original
>  - Fixes the vehicle flicker
>  - Smaller!
>  - Annotated source is provided
>  - Only draws 10 visible tile rows
>  - Uses a mini HGR Y lookup table
> 
> CALL-151
> - - - - - 8< - - - - -
> A00:38 A5 07 E9 05 85 D7 38
> A08:A5 06 E9 09 85 EF A2 00
> A10:86 09 A5 EF 85 D6 A0 00
> A18:BD 90 0A 85 FE BD 9C 0A
> A20:85 FF A2 00 86 FC A5 D6
> A28:C5 06 D0 0C A5 D7 C5 07
> A30:D0 04 A5 08 D0 1E A5 D6
> A38:C9 40 B0 1B A5 D7 C9 40
> A40:B0 15 6A 66 FC 6A 66 FC
> A48:09 10 85 FD A5 FC 65 D6
> A50:85 FC A1 FC 29 F0 AA A5
> A58:FE 85 FA A5 FF 85 FB BD
> A60:00 09 91 FA C8 BD 00 08
> A68:91 FA 88 E8 18 A5 FB 69
> A70:04 C9 40 90 E8 A5 FA 49
> A78:80 C5 FE D0 DC E6 D6 C8
> A80:C8 C0 28 D0 9D E6 D7 A6
> A88:09 E8 E0 0A D0 82 60 EA
> 0A90:00 00 00 00 28 28 28 28 50 50 50 50
> 0A9C:20 21 22 23 20 21 22 23 20 21 22 23
> - - - - - 8< - - - - -
> A00G
> 
> If you Want to patch a copy of your "Master" disk
>  UNLOCK DRAW 64.oBJ
>  BLOAD DRAW 64.OBJ,A$A00
> Enter the above in...
>  BSAVE DRAW 64.OBJ,A$A00,L$200
>  LOCK DRAW 64.oBJ
> 
> Notes:
>  For some reason DRAW 64.OBJ also includes
>  an Applesoft DRAW shape table at $B00.
>  Don't ask me why since the game also
>  loads them via:
>    BLOAD OUT.SHAPES,A$B00
> 
> Annotated Optimized DrawMap ver 4c:
>  0A00:38       DrawMap   SEC
>  0A01:A5 07              LDA $7       ; Player.Y
>  0A03:E9 05              SBC #5       ; World to MapWindow.Top
>  0A05:85 D7              STA TileY    ; CenterY (10 Tiles Tall)
>  0A07:38                 SEC
>  0A08:A5 06              LDA $6       ; Player.X
>  0A0A:E9 09              SBC #9       ; World to MapWindow.Left
>  0A0C:85 EF              STA TileLeft ; CenterX (20 Tiles Across)
>  0A0E:A2 00              LDX #0       ;
>  0A10:86 09    drawRow   STX Row      ; Row is zero based
>  0A12:A5 EF              LDA TileLeft
>  0A14:85 D6              STA TileX
>  0A16:A0 00              LDY #0       ; NextDrawColumn
>  0A18:BD 90 0A           LDA HGRYLO,X
>  0A1B:85 FE              STA $FE
>  0A1D:BD 9C 0A           LDA HGRYHI,X
>  0A20:85 FF              STA $FF
>  0A22:A2 00    drawCols  LDX #0       ; default Water Tile (00)
>  0A24:86 FC              STX MAP.PTR  ; Init MapTilePtrL
>  0A26:A5 D6              LDA TileX
>  0A28:C5 06              CMP PlayerX  ; CurTileX == PlayerX ?
>  0A2A:D0 0C              BNE clipX2
>  0A2C:A5 D7              LDA TileY    ; CurTileY == PlayerY ?
>  0A2E:C5 07              CMP PlayerY
>  0A30:D0 04              BNE clipX
>  0A32:A5 08    doVehicle LDA Vehicle  ; ($08) is always > #$80
>  0A34:D0 1E              BNE maskTile ; could also use A6 08 LDX
> Vehicle 30 21 BMI drawTile for speed
>  0A36:A5 D6    clipX     LDA TileX
>  0A38:C9 40    clipX2    CMP #40      ; Win.X > 64 ?
>  0A3A:B0 1B              BCS drawTile
>  0A3C:A5 D7              LDA TileX    ; 0 00cdefgh 00000000
>  0A3E:C9 40              CMP #40      ; Win.Y > 64 ?
>  0A40:B0 15              BCS drawTile ; get a free Clear Carry :)
>                        ; CLC        carry Acc/$FD  $FC
>  0A42:6A                 ROR          ; h 000cdefg 00000000
>  0A43:66 FC              ROR $FC      ; 0 000cdefg h0000000
>  0A45:6A                 ROR          ; g 0000cdef h0000000
>  0A46:66 FC              ROR $FC      ; 0 0000cdef gh000000
>  0A48:09 10              OR  #10      ; $FD.$FC max = $0FFF
>  0A4A:85 FD              STA $FD      ; Map64x64 at $1000..$1FFF
>  0A4C:A5 FC              LDA $FC
>  0A4E:65 D6              ADC $D6
>  0A50:85 FC              STA $FC
>  0A52:A1 FC              LDA ($FC,X)  ; CurTile
>  0A54:29 F0    maskTile  AND #F0
>  0A56:AA                 TAX
>  0A57:A5 FE    drawTile  LDA $FE
>  0A59:85 FA    drawHalf  STA $FA
>  0A5B:A5 FF              LDA $FF
>  0A5D:85 FB    draw8     STA $FB
>  0A5F:BD 00 09           LDA $0900,X
>  0A62:91 FA              STA ($FA),Y  ; draw even col
>  0A64:C8                 INY
>  0A65:BD 00 08           LDA $0800,X
>  0A68:91 FA              STA ($FA),Y  ; draw odd col
>  0A6A:88                 DEY
>  0A6B:E8                 INX
>  0A6C:18                 CLC
>  0A6D:A5 FB              LDA $FB
>  0A6F:69 04              ADC #4       ; next screen row
>  0A71:C9 40              CMP #40      ; done 8 rows?
>  0A73:90 E8              BCC draw8
>  0A75:A5 FA              LDA $FA
>  0A77:49 80              EOR #80      ; optimization: ADC #80
>  0A79:C5 FE              CMP $FE
>  0A7B:D0 DC              BNE drawHalf ; done all 16 scanlines?
>  0A7D:E6 D6              INC TileX
>  0A7F:C8                 INY
>  0A80:C8                 INY
>  0A81:C0 28              CPY #$28     ; drawn all tiles acrosss?
>  0A83:D0 9D              BNE drawCols
>  0A85:E6 D7              INC TileY
>  0A87:A6 09              LDX Row      ;
>  0A89:E8                 INX
>  0A8A:E0 0A              CPX #A       ; done 10 tile rows?
>  0A8C:D0 82              BNE drawRow  ; ... must ... reach ...
>  0A8E:60
>  0A8F:EA                 NOP          ; pad to $A90
>  0A90:00 00 00 00        HGRYLO DB 00,00,00,00 ; dest address of every
> 16 scanlines
>  0A94:28 28 28 28               DB 28,28,28,28
>  0A98:50 50 50 50               DB 50,50,50,50
>  0A9C:20 21 22 23        HGRYHI DB 20,21,22,23
>  0AA0:20 21 22 23               DB 20,21,22,23
>  0AA4:20 21 22 23               DB 20,21,22,23
> 
> Applewin symbol commands
>  DB PlayerX       6
>  DB PlayerY       7
>  DB Vehicle       8 // player vehicle sprite #
>  DB Row           9
>  DB TileX        D6 // = (PlayerX - 9)
>  DB TileY        D7 // = (PlayerY - 5)
>  DB TileLeft     EF
>  DB HGR.DST      FA
>  DB MAP.PTR      FC
>  DB HGR.BASE     FE
>  DB HGRYLO      A90:A9B
>  DB HGRYHI      A9C:AA7
>  DB TilesOdd    800
>  DB TilesEvn    900
>  DB Map        1000
> 
>  SYM DrawMap    = A00
>  SYM drawRow    = A10
>  SYM drawCols   = A22
>  SYM drawPlayer = A32
>  SYM clipX      = A36
>  SYM clipX2     = A38
>  SYM maskTile   = A54
>  SYM drawTile   = A57
>  SYM drawHalf   = A59
>  SYM draw8      = A5D
> 
> In order to get any more speed the tiles
> at $800 .. $9FF would need to be swizzled.
> 
> Ideally, there would also be 4 map scrollers that scroll
>  the HGR page "in-place"
>  - scroll left
>  - scroll right
>  - scroll down
>  - scroll up
> And the corresponding 4 draw partial map functions:
>  - draw right edge
>  - draw left edge
>  - draw top edge
>  - draw bottom edge
> 
> This is left as an exercise for the reader. =)
> 
> 
> 4. Annotated original DrawMap
> = = = = = = = = = = = = = = =
> 
> If you want a "good" example of how NOT to
> write a 2D map renderer here is my
> annotated Ultima 1 for the original Draw Map code.
> 
> File: DRAW 64.OBJ
> 
>  0A00:38        DrawMap SEC
>  0A01:A5 07             LDA $7      ; Player.Y
>  0A03:E9 05             SBC #5      ; World to MapWindow.Top
>  0A05:85 D7             STA $D7     ; CurTile.Y
>  0A07:A9 00             LDA #00
>  0A09:85 FE             STA $FE     ; HGR.BASE (dest tile ptr)
>  0A0B:A9 20   drawTriad LDA #20     ; every 64 rows dest Y start
> address $20xx
>  0A0D:85 FF             STA $FF     ;
>  0A0F:A0 00     drawRow LDY #0      ; left column
>  0A11:38                SEC
>  0A12:A5 06             LDA $06     ; Player.X
>  0A14:E9 09             SBC #9      ; World to MapWindow.Left
>  0A16:85 D6             STA $D6     ; CurTile.X
>  0A18:A9 09     drawCol LDA #9      ; start with even column of source
> TileAddr.Hi
>  0A1A:AD 68 0A          STA TileAdrH; Self Modifying Code:  GET
> TileScanLine
>  0A1D:A9 00             LDA #0      ; CurTile = default to Water Tile
> (0)
>  0A1F:48                PHA         ; save curtile
>  0A20:A5 D6             LDA $D6     ; Win.X > 64 ?
>  0A22:C9 40             CMP #40
>  0A24:B0 28             BCS ClipWin ; $0A4E
>  0A26:A5 D7             LDA $D7     ; Win.Y > 64 ?
>  0A28:c9 40             CMP #40
>  0A2A:B0 22             BCS ClipWin ; $0A4E
>  0A2C:68                PLA         ; toss CurTile
>  0A2D:85 FD             STA $FD     ; Init MapTilePtrH
>  0A2F:A5 D7             LDA $D7     ; MapTileAddr(y,x) = $1000 + y *
> 64 + X
>  0A31:0A                ASL         ; inefficient y*64
>  0A32:0A                ASL
>  0A33:0A                ASL
>  0A34:26 FD             ROL $FD
>  0A36:0A                ASL
>  0A37:26 FD             ROL $FD
>  0A39:0A                ASL
>  0A3A:26 FD             ROL $FD
>  0A3C:0A                ASL
>  0A3D:26 FD             ROL $FD
>  0A3F:65 D6             ADC $D5     ; +x
>  0A41:84 FC             STA $FC
>  0A43:A5 FD             LDA $FD
>  0A45:69 10             ADC #10     ; #0..#0FFF -> #1000..#1FFF
>  0A47:85 FD             STA $FD
>  0A49:A2 00             LDX #0
>  0A4B:A1 FC             LDA ($FC,X) ; get tile from map
>  0A4D:48                PHA
>  0A4E:68        clipWin PLA         ; get CurTile
>  0A4F:09 0F             ORA #0F     ; Tile.Height = 16
>  0A51:AA                TAX
>  0A52:86 09             STX $09     ; not needed -- save CurTile??
>  0A54:A9 80      doTile LDA #80     ; start HGR address Low bottom 8
> scanlines
>  0A56:A6 09             LDX $09
>  0A58:48         doHalf PHA         ; save HGR.DST Low
>  0A59:18                CLC
>  0A5A:65 FE             ADC $FE     ; add HGR.BASE.LOW
>  0A5C:85 FA             STA
>  0A5E:A9 1C             LDA #1C     ; start at dest bottom half = 8
> scanline
>  0A60:48          draw8 PHA
>  0A61:18                CLC
>  0A62:65 FF             ADC $FF
>  0A64:85 FB             STA $FB
>  0A66:BD 00 07          LDA $0700,X ; source tile byte
>  0A69:CA                DEX
>  0A6A:91 FA             STA ($FA),Y ; dest HGR 7-pixels
>  0A6C:38                SEC
>  0A6D:68                PLA
>  0A6E:E9 04             SBC #4      ; move to previous HGR scanline
>  0A70:10 EE             BPL draw8   ; $0A60
>  0A72:38                SEC
>  0A74:E9 80             SBC #80     ; adjust dest scanline ptr for top
> 8
>  0A76:10 E0             BPL doHalf  ; $0A58
>  0A78:C8                INY         ; move to next HGR column
>  0A79:CE 68 0A          DEC TileAdrH; move to source tile prev column
> data
>  0A7C:AD 68 0A          LDA TileAdrH; $A68
>  0A7F:C9 08             CMP #08     ; done even source col of tile?
>  0A81:10 D1             BPL doTile  ; $0A54
>  0A83:E6 D6             INC $D6     ; TileX
>  0A85:C0 28             CPY #28     ; Drawn all 40 columns?
>  0A87:D0 8F             BNE drawCol ; $0A18
>  0A89:E6 D7             INC $D7     ; TileY
>  0A8B:E6 FF             INC $FF     ; HGR.BASE.HI = constant delta $1
> every 16 scanlines
>  0A8D:A5 FF             LDA $FF
>  0A8F:C9 24             CMP #24     ; drawn 64 scanlines?
>  0A91:F0 03             BEQ done64  ; $0A96
>  0A93:4C 0F 0A          JMP drawRow ; $0A0F
>  0A96:18         done64 CLC
>  0A97:A5 FE             LDA $FE     ; HGR.BASE.LO
>  0A99:69 28             ADC #28
>  0A9B:85 FE             STA $FE
>  0A9D:C9 78             CMP #78     ; done all 192 rows?
>  0A9F:F0 03             BEQ vehicle ; $0AA4
>  0AA1:4C 0B 0A          JMP drawTriad ; $0A0B
> 
>  0AA4:A9 28     vehicle LDA #28     ; y=#50 + x=#12 = $2128 + $12 =
> $213A
>  0AA6:85 FE             STA $FE
>  0AA8:A9 21             LDA #21
>  0AAA:85 FF             STA $FF
>  0AAC:A0 12             LDY #12     ; start $12 columns over (center
> of screen)
>  0AAE:A5 08             LDA $8      ; PlayerVehicle
>  0AB0:09 0F             ORA #0F     ; Tile.Height = 16
>  0AB2:85 09             STA $09     ; CurTile
>  0AB4:A9 09             LDA #09     ; start with even column of source
> TileAddr.Hi
>  0AB6:8D CD 0A          STA vehTile+2
>  0AB9:A9 80     vehTile LDA #80     ; start HGR address Low bottom 8
> scanlines
>  0ABB:A6 09             LDX $09
>  0ABD:48        vehHalf PHA
>  0ABE:18                CLC
>  0ABF:65 FE             ADC $FE
>  0AC1:85 FA             STA $FA
>  0AC3:A9 1C             LDA #1C     ; start at dest bottom 8 scanline
>  0AC5:48        vehRow  PHA
>  0AC6:18                CLC
>  0AC7:65 FF             ADC $FF
>  0AC9:85 FB             STA $FB
>  0ACB:BD 00 07          LDA $0700,X
>  0ACE:CA                DEX
>  0ACF:91 FA             STA ($FA),Y
>  0AD1:38                SEC
>  0AD2:68                PLA
>  0AD3:E9 04             SBC #4
>  0AD5:10 EE             BPL vehRow  ; $0AC5
>  0AD7:38                SEC
>  0AD8:68                PLA
>  0AD9:E9 80             SBC #80
>  0ADB:10 E0             BPL vehHalf ; $0ABD
>  0ADD:C8                INY         ; move to next HGR column
>  0ADE:CE CD 0A          DEC vehTile+2
>  0AE1:AD CD 0A          LDA vehTile+2
>  0AE4:C9 08             CMP #8      ; done even source col of tile?
>  0AE6:10 D1             BPL vehTile ; $0AB9
>  0AE8:60                RTS
> 
> AppleWin Symbols
>  DB PlayerX       6
>  DB PlayerY       7
>  DB Vehicle       8 // player vehicle sprite #
>  DB CurTile       9
>  DB TileX        D6 // = (PlayerX - 9)
>  DB TileY        D7 // = (PlayerY - 5)
>  DB HGR.DST      FA
>  DB MAP.PTR      FC // Tile from world to fetch
>  DB HGR.BASE     FE
>  DB SpritesODD  800
>  DB SpritesEVEN 900
>  DB Map        1000 // 64x64 $1000..$1FFF
> 
>  SYM DrawMap    = A00
>  SYM drawTriad  = A0B
>  SYM drawRow    = A0F
>  SYM drawCol    = A18
>  SYM clipWin    = A4E
>  SYM doTile     = A54
>  SYM doHalf     = A58
>  SYM draw8      = A60
>  SYM TileAdrH   = A68
>  SYM done64     = A96
>  SYM vehicle    = AA4
>  SYM vehTile    = AB9
>  SYM vehHalf    = ABD
>  SYM vehRow     = AC5
>  SYM VehAdrH    = ACD
> 
> And
>   reg Y = 0 .. $28 (column to draw)
> 
> Example Water Tile:
>  Row  Hi-Res Data   Source
>  [00] $2000: 80 80 ($900 $800)
>  [01] $2400: 80 80 ($901 $801)
>  [02] $2800: D0 A0 ($902 $802)
>  [03] $2C00: 85 8A ($903 $803)
>  [04] $3000: 80 80 ($904 $804)
>  [05] $3400: 80 80 ($905 $805)
>  [06] $3800: 85 82 ($906 $806)
>  [07] $3C00: D0 A8 ($907 $807)
>  [08] $2080: 80 80 ($908 $808)
>  [09] $2480: 80 80 ($909 $809)
>  [0A] $2880: 90 A8 ($90A $80A)
>  [0B] $2C80: C5 82 ($90B $80B)
>  [0C] $3080: 80 80 ($90C $80C)
>  [0D] $3480: 80 80 ($90D $80D)
>  [0E] $3880: C1 82 ($90E $80E)
>  [0F] $3C80: 94 A8 ($90F $80F)
> 
> 
> 5. Monster Sprites
> = = = = = = = = =
> 
> NPC Sprites
>  HGR
>  BLOAD OUT.SHAPES,D1,A$B00
>  SCALE=1:ROT=0
>  POKE 232,0:POKE 233,11
>  NEW
>  HCOLOR=3:DRAW 1 at 0, 0:DRAW 2 AT 14, 0:DRAW 3 AT 28, 0
>  HCOLOR=3:FOR Y=16 TO 47:HPLOT 0,Y TO 41,Y:NEXT
>  HCOLOr=0:DRAW 4 AT 0,16:DRAW 5 AT 14,16:DRAW 6 AT 28,16
>  HCOLOr=0:DRAW 4 AT 0,32:DRAW 5 AT 14,32:DRAW 6 AT 28,32
>  HCOLOR=3:DRAW 1 at 0,16:DRAW 2 AT 14,16:DRAW 3 AT 28,16
> 
> Partial Annotated Ultima 1 (original) Draw NPC Data
>  B00:06 00     // 6 Sprites
>  B02:0E 00     // #1 @ B0E OR mask Sea Serpent  - drawn over water
>  B04:35 00     // #2 @ B35 OR mask Mage         - drawn over land
>  B06:6D 00     // #3 @ B64 OR mask Ent          - drawn over forest
>  B08:82 00     // #4 @ B82 AND mask Sea Serpent - drawn over water
>  B0A:CF 00     // #5 @ BCF AND mask Mage        - drawn over land
>  B0C:2C 01     // #6 @ C2C AND mask Ent         - drawn over forest
> 
> Tiles:
>  $00 Water
>  $10 Land
>  $20 Forest
> 
> AppleWin
>  DW NPC.SPRITES B00:B0D
> 
> Sprites used by:
>   FILE: OUT MOVE
>   LINE 1211 T1 = maptile(x,y)/16
>     T1 = 0, 1, 2
>   LINE 1213 draw map: if T1 > 2 T1 = 1
>   LINE 1214
>      HCOLOR=0:DRAW T1+4 AT (9+DX)*14,(5+DY)*16
>      HCOLOR=3:DRAW T1+1 AT (9+DX)*14, 16*(DY+5)
>   Note: The player/vehicle/avatar is always at ScreenTile 9,5.
> 
> 
> 6. Bootstrapping
> = = = = = = = =
> 
> If you are using the cracked disk image
> You will need to patch the Applesoft
> program so you can view the BASIC source.
>  LOAD INTRO
>  806:0D
>  89B:0D
>  LIST
> 
>  RUN ULTIMA
>  RUN INIT DISPLAY,D1
>      LOMEM:30730
>   IN = -1
>      BLOAD CUR.PIC
>      BLOAD B.CUR,A$B00
>   IN = -2
>      BLOAD CUR.PIC
>      BLOAD B.CUR,$B00
>   IN <> -3
>      BLOAD ULTSHAPES,A$800,D1
>      BLOAD DRAW 64.OBJ
>      BLOAD TWN.CAS.SHAPES,A3700
>   RUN OUT MOVE,D1
> 
> 
> 
> 7. Applesoft Variables
> = = = = = = = = = = =
> 
> Note: Applesoft only uses the first 2 characters
> of a variable name. FA and FACE all
> refer to the same variable.
> 
>  FILE: OUT MOVE
> 
>   10000 Load World Map
>     Variable: FACE = which world
>    PRINT D$;"BLOAD BTERRA";FA:
>     BLOAD BTERRA0,A$1000
>     BLOAD BTERRA1,A$1000
>     BLOAD BTERRA2,A$1000
>     BLOAD BTERRA3,A$1000
> 
>  Line 2100 of Gets the tile# with
>   INT( PEEK (4096 + TY*64 + TX) /16) );
> 
> 
>  Line 2150 updates the world map with the vehicle
>   2150 RED(3) = T-8:
>      POKE (4096+TY*64+TX),
>      (PEEK(4096+TY*64+TX) - INT(PEEK(4096+TY*64+TX)/16)*16) * 16:
>      GOSUB 1299: RETURN
> Not a bug, but an perfect example why Applesoft is slow...
> ..  because there is no built-in MOD function.
>  Context: Exit vehicle (Line 2100 = Board Vehicle/craft)
> What this code does: multiply map tile * 16
> i.e. If MapTile(x,y) = $AB
>  Then MapTile(x,y) = $B0
> i.e.
> A = 33:PRINT (A-INT(A/16)*16)*16
> FOR H=0 TO 15:FOR L=0 TO 15:B=H*16+L:PRINT B;" -> ";(B-INT(B/
> 16)*16)*16:NEXT:NEXT
> 
> For speed the code should of only set $08
> 
> 
>  If you board a space machine this Applesoft program is run:
>    2135 RUN SPA MOVE
>  Enter town
>     500 RUN TWN MOVE
>  Enter castle
>     520 RUN CAS MOVE
>  Enter Dungeon
>    530 RUN DNG MOVE
>  Draw map
>    91  ... GOSUB 1297 ...
>    1213 CALL 2560
>    1299 FOOD -= (7 - RED(3)) / 14
>         TIME += (7 - RED(3)) / 7
>         POKE 6,XX: POKE 7,YY: POKE 8,(RED(3)+8)*16: CALL 2560
> 
> Main Loop
>  600 Print Player Stat Summary, Life, Food, Experience,Gold
>  800 "COMMAND:", check for keypress
>  840 "PASS" -- Time-out after 2 seconds
>  1000 main loop
>       Check if Life<0 Food<0
>       ZZ = 141 $8D return = up arrow
>       ZZ = 175 $AF /      = down arrow
>       ZZ = 149 $95 ->     = right arrow
>       ZZ = 136 $88 <-     = left arrow
>       Call 1200, jump 1040
>  1030 Handle normal key input - jump table
>  1040 call 6000, jump 1000
> Commands:
>  2000  A = Attack
>  2100  B = Board craft
>  2200  C = Cast (readied spell)
>          2210  0="POTENTITIS-LAUDIS", 33% for which random effect
>            i) banish all monsters!
>            ii) If almost dead (life < 10), set life = 10
>            iii) If almost starving (food < 10), set food = 10
>          2290 1, 2=Dungeon Only
>          2220 3="DELICIO-ERU-U", magic-missile; dmg=C(5) +2 *
> (7<weapon<12)
>          2290 4, 5, 6, 7, 8, 9 Dungeon
>          2230 10="INTERFICIO-NUNC", attack monster
>  2300  D = Drop = n/a
>  2400  E = Enter town/castle
>  2500  F = Fire vehicle weapns.  20% chance to miss.
>            Vehicle: 4 = Cannons
>            Vehicle: 5 = Lasers
>  2600  G = Get = n/a
>  2700  H = n/a
>  2800  I = Inform and search
>  2900  J = n/a
>  3000  K = n/a
>  3100  L = Lag Time (0-9)
>  3200  M = n/a
>  3300  N = n/a
>  3400  O = Open
>  3500  P = n/a
>  3600  Q = "Quit & Save Game"
>    BSAVE BEVERY,A$7800,L$1E01
>    BSAVE BTERRA,A$1000,L$1000
>  3700  R = Ready Weapon, Armor, Spell
>  3800  S = Steal -- n/a outdoors
>  3900  T = Talk -- n/a outdoors
>  4000  U = Unlock
>  4100  V = n/a
>  4200  W = n/a
>  4300  X = Exit vehicle
>  4400  Y = n/a
>  4500  Z = Inventory
>  6000 Combat - attacked by mob
>  6400 Dead state
> 
> Variables (some initialized in Line 280)
>  FACE - which land/world to enter next
>    Called on movement
>       1215: IF TX + DX >  75 THEN GOSUB 980, 900, 990: JUMP 1297
>       1216: IF TX + DY < -12 THEN ...
>       1217: IF TY + DY >  75 THEN ...
>       1218: IF TY + DY < -10 THEN ...
>    0  900: FACE++:  902: TX = -10
>    1  920: FACE--:  922: TX =  73
>    2  940: FACE-2:  942: TY = -10
>    3  960: FACE+2:  962: TY = 73
>    World saved at 985
> 
>  RED(0) = Spell Readied
>  RED(1) = Weapon Readied
>  RED(2) = Armor Readied
>  RED(3) = Vehicle Boarded
>  G1 = # of Red Gem
>  G2 = # of Green Gems
>  G3 = # of Blue Gems
>  G4 = # of White Gems
>  NA$ = Player name
>  SPE() = # of each spell
> 
>  C(0) = Player Life/Health, Init = 100, Line #720
>  C(1) = Strength
>  C(2) = Agility
>  C(3) =
>  C(4) =
>  C(5) = Wisdom
>  C(6) = Intelligence
>  C(7) = Player Gold, Init = 100, File: INIT DISPLAY, Line #720
>  C(8) = Race, INIT DISPLAY, Line
>    1 c(6) + 5
>    2 c(2) + 5
>    3 c(1) + 5
>    4 c(5) + 10, c(1) - 5
>  C(9) = Type, INIT DISPLAY Line 630
>    1 c(1) + 10, c(2) + 10
>    2 c(5) + 10
>    3 c(6) + 10
>    4 c(2) + 10
> 
> Race   Attribute Bonus
> Human  +5 Intelligence
> Elf    +5 Agility
> Dwarf  +5 Strength
> Bobbit +10 Wisdom; -5 Strength
> 
> Class/Profession
> Attribute Bonus
> Fighter +10 Strength; +10 Agility
> Cleric  +10 Wisdom
> Wizard  +10 Intelligence
> Thief   +10 Agility
> 
>  FOOD = Player Food
>  EPNT = Player Experience
>  AN = Monster Count!
>  AM = Monster Type
>  MN$()
>   6 = Ness Creature
>   7 = Giant Squid
>   10 = Hood
>   12 = Hidden Archer
>   19 = Evil Ranger
>   20 = Wandering Warlock
>   30 = Gelationous Cube
>   33 = Lizard Man
>   32 = Mimic
>   39 = Wraith
>   41 = Invisible Seeker
>  TDT
>   44 = The Souther Sign Post
>  FO  =  ?
>  TX = PlayerX
>  TY = PlayerY
> 
>  Player disk name, File: INIT DISPLAY, Line 760, has typo??  PRINT NANE
> $
>    OPEN INFO
>    WRITE INFO
>    PRINT NANE$;" DISK "; CHR$(48):
>    CLOSE INFO
>  Player disk name read, Line 10000
>    OPEN INFO
>    READ INFO
>    INPUT Q$
>    CLOSE INFO
> 
>    TIME = 0
>    WRLD = 0
>    FACE = 0
>    EPNTS = 1
>    INOUT = 0
>    LEV = 0
>    TX = 40
>    TY = 40
>    PX = 0
>    PY = 0
>    DX = 0
>    DY = 0
>    FOOD = 100
>    PW(1) = 2
>    ARM(1) = 1
>    RED(1) = 1
>    RED(2) = 1
> 
> 8. BUGS
> = = = =
> 
> Death: When you die you will be put to a random map
>  location.  Sometimes you will be stuck in the middle
>  of the ocean unable to move.  (Guess swimming was
>  not invented yet.)
> FILE:OUT MOVE
> LINE:6400
> 6400 PRINT NA$;", YOU ARE DEAD.":
>     PRINT "ATTEMPTING RESURRECTION!":
>     FOR X=1 TO 15: PW(X)=0: NEXT:
>     IN=0: RE(1)=0: RE(3)=0: C(7)=0: FO=20:
>     TX = INT(RND(1)*64):
>     TY = INT(RND(1)*64):
>     C(0) = 99: AM = 0
> 6402 FOR X=1 TO 2000:NEXT:GOSUB 600:GOSUB 1297:GOTO 1000
> 
> Add this line:
> 6401 TX = INT(RND(1)*48)+8: TY = INT(RND(1)*48)+8:
>      T  = INT(PEEK(4096+TY*64+TX)/16):IF T=0 OR T=3 THEN 6401
> 
> 
> 9. Misc.
> = = = =
> Since the original came out in 1981 does this make
> these notes the oldest fan-made patches for a 31 year
> old game? =)