The current URL is datacrystal.tcrf.net.
Illusion of Gaia/Notes
The following article is a Notes Page for Illusion of Gaia.
Map assets
Assets for each map are loaded based on a table at $0d8000. The engine seeks through this table when a map is loaded, looking for the map's ID header.
The format of a map's entry in the table is
db MapNum : db $00
followed by one or more of the following header cards:
Card | Arguments | Notes |
---|---|---|
db $02
|
db PpuSettingsIndex
|
Indexes pointers to PPU register settings at $818000. |
db $03
|
db SrcOffset,SizeW
|
Load VRAM character data. Writes to VRAM after decompressing (if needed) to $7e7000. The base VRAM word-address is $2000 (byte-address $4000) if IsSprites=0, or $4000 (byte-address $8000) if IsSprites=1. |
db $04
|
db SrcOffset,SizeW
|
Load CGRAM data. Writes to the CGRAM buffer at $7f0a00. |
db $05
|
db SrcOffset,SizeW
|
Load BG metatile mappings, which join four 8x8 px VRAM characters as a 16x16 px metatile. Only 8 bits of the VRAM addresses are used; the four high bits in each tile set its collision type. Mappings are written to $7e2000 for MapLayer, $7e2800 for EffectLayer. Layers=1 for Map, 2 for Effect, 3 for both. |
db $06
|
db Layer
|
Load metatile tilemap for Layer=1 (MapLayer, conventionally BG1) or Layer=2 (EffectLayer, conventionally BG2). Tilemap is written to $7ea000 for MapLayer, $7ec000 for EffectLayer. |
db $10
|
dw SizeB : db Dummy
|
Load metasprite / spriteset / sprite tile connectivity data. Data is written to $7e4000. |
db $11
|
db MusicId,RoomGroup
|
Load music. MusicId sets which music should be resumed by actors that interrupt the normal music. If $7e06f6 is nonzero and not equal to RoomGroup, this card does nothing, and any pre-existing music continues without interruption; the Gold Ship uses this to preserve music between outside and inside. $7e06f6 is zeroed after this card is read. |
db $13
|
db FlagIndex,DestLabel
|
If game flag FlagIndex is set, jump to DestLabel. |
db $14
|
db Label
|
Set jump label here; jump here from another entry using $13 or $15. |
db $15
|
db DestLabel
|
Jump to DestLabel, unconditionally. |
Always uncompressed: palette data (card $04), music data (card $11). Other data can be compressed in Quintet's LZ format, in which case the Size bytes here are ignored. If the data has #$0000 for its "compressed size", it is taken as uncompressed and the Size bytes here are used.
Most of these can occur in any order and any number, and will override previous data in a logical way. However, $03 with IsSprites=0 must precede $05/$06 in order to set up VRAM and the decompression buffer, and $05 for each layer must precede $06 for that layer.
Map design tables
Actor lists
Actor lists per map start at $0c8200. Each valid map has an actor list, addressed by indexed pointers at $0c8000.
Entries in actor lists are of the form:
db PosX,PosY,Param : dl EntryPtr-3 : db StatsIndex[,MonsterId,DeathActionIndex]
PosX,PosY
- usually the spawn point, in tiles from the map's northwest corner. Some actors override and use for different purposes.
Param
- if odd, usually represents an actor-specific parameter, e.g. "contents of this Dark Space"; right-shifted 1 bit and stored in actor-page $0E. If even, bit-3 is cleared and Param becomes the high byte of the actor's sprite OAM override.
EntryPtr
- address where actor code execution will start. The list points 3 bytes earlier, to a byte+word header: starting sprite ID, then starting actor-page $10/$11 (physics and rendering flags).
StatsIndex
- monster's stat block, indexing a table of HP/STR/DEF/DP at $81abf0 + 4*StatsIndex. Actors not involved in combat should have StatsIndex=0, in which case MonsterId and DeathActionIndex are omitted and the next entry in the list begins.
MonsterId
- labels the monster to track its death through map transitions. If MonsterId is 0, it always respawns. Otherwise, it stays dead until the death tracking flag is cleared: usually by going to the world map, by player death, or manually.
DeathActionIndex
- indexes a map rearrangement (BG layer tile copy). When the monster dies, the map rearrangement is triggered automatically, as if by calling cop #$32 : db DeathActionIndex : cop #$33.
- The actor list ends with $FF, followed by the name of the map (if any), followed by $CA.
Thinker lists
Thinker lists per map start at $0ce7e5. Each valid map has a thinker list, addressed by indexed pointers at $0ce5e5.
Entries in thinker lists are of the form:
db Param : dl EntryPtr-2
Param
is a thinker-specific parameter, e.g. "palette bundle index". EntryPtr
is the code execution start point. The list points two bytes earlier, to a word header copied into $7f000e,x. Flag #$0004 in the header means execute-after-actors if set, before-actors if clear.
Actor code
Each actor has a base address / ID, which indexes $30 blocks of RAM at each of $7e0000,x, $7f0000,x, and $7f1000,x. The actor list is a doubly linked list. Each frame, the list is processed in forward order running actor code, then post-processed (e.g. collision detection) in reverse.
Each actor has a dynamic code entry point. The handler invokes code by the equivalent of jsl EntryPtr.
At entrancy, actor code should assume that the processor state is:
m=0, x=0, d=0, i=1 X = ActorID D = ActorID DBR = $81
Actor code must restore this state before it exits, except that X need not be restored. Actor code must exit via the equivalent of rtl at the stack level of entrancy.
At $7e0000,x (which is also the direct page) most memory is used by the engine:
$7e:00 long EntryPtr Pointer to code that will be run on this actor's turn. $7e:03 byte dummy03 Dummy byte, makes it easier to handle EntryPtr. $7e:04 word PrevId Pointer to actor (= actor ID) before this one in the list. $7e:06 word NextId Pointer to actor (= actor ID) after this one in the list. $7e:08 word WaitTime Frames to wait before running code again, e.g. animation timer. $7e:0a long ArgPtr While in a COP, pointer to COP argument or return address. $7e:0d byte dummy0d Dummy byte, makes it easier to handle ArgPtr. $7e:0e word OamXor XOR'd with OAM for palette changes etc. if actor has a sprite. $7e:10 word Flags10 Actor state flags. $7e:12 word Flags12 Actor state flags. $7e:14 word PosX Actor x position, pixels, from northwest corner of map. $7e:16 word PosY Actor y position, pixels, from northwest corner of map. $7e:18 word OffsX Offset of sprite from PosX, if actor has a sprite. $7e:1a word OffsY Offset of sprite from PosY, if actor has a sprite. $7e:1c word OffsXMir As OffsX, if the h-mirror flag in $0e were flipped. $7e:1e word OffsYMir As OffsY, if the v-mirror flag in $0e were flipped. $7e:20 byte HitboxW Hitbox size, West (-x) direction. If no hitbox, free. $7e:21 byte HitboxE Hitbox size, East (+x) direction. If no hitbox, free. $7e:22 byte HitboxN Hitbox size, North (-y) direction. If no hitbox, free. $7e:23 byte HitboxS Hitbox size, South (+y) direction. If no hitbox, free. $7e:24 word Free24 Free memory. $7e:26 word Free26 Free memory. $7e:28 word SprIdx Sprite (graphics) index, if actor has a sprite. $7e:2a word SprFrame Animation frame of sprite, if actor has a sprite. $7e:2c word MoveX Movement data or address of current move pattern, x direction. $7e:2e word MoveY Movement data or address of current move pattern, y direction.
At $7f0000,x is a mix of engine, subroutine, and free memory:
$7f:00 dwrd AnimScr1 Scratch bytes for sprite animation COPs. $7f:04 word RetPtr1 Return pointer set by some COPs. $7f:06 long SprSetPtr Pointer to spriteset (e.g. "Diamond Mine"), which $7e:28 indexes. $7f:09 byte dummy09 Dummy byte for SprSetPtr. $7f:0a word ChatPtr How actor interacts with player. Specifics depend on flags. $7f:0c word SprMetPtr Pointer to metasprite (i.e. sprite tile assembly data). $7f:0e word AnimScr2 Scratch bytes for sprite animation COPs. $7f:10 word OrbitAng Not reserved. Orbit routines use this as orbiting angle. $7f:12 word OrbitDia Not reserved. Orbit routines use this as orbiting diameter. $7f:14 word LoopCntr Not reserved. Some COPs use this as a loop counter. $7f:16 word SprTimer Timer for sprite animation COPs. $7f:18 word MoveXAlt Alternate MoveX, meaning depends on context. $7f:1a word MoveYAlt Alternate MoveY, meaning depends on context. $7f:1c word ParentId If set, and conditions are met, actor dies with its parent. $7f:1e word RetPtr2 Return pointer set by some COPs. $7f:20 word StatsPtr Pointer to monster's HP/STR/DEF/DP stats. $7f:22 word EnemyNum Dungeon-level enemy number for flagging killed enemies. $7f:24 word DeathIdx Index of map rearrangement script to perform on death. $7f:26 word CurrHp Current HP. $7f:28 word IframeCtr Abs. value is iframes; typically positive means stunned. $7f:2a word Flags7F2A Actor state flags. $7f:2c word MoveScr1 Scratch bytes for some movement COPs. $7f:2e word MoveScr2 Scratch bytes for some movement COPs.
Memory at $7f1000,x is mostly unreserved:
$7f1000 word HitPtr When hit by player, EntryPtr is set to HitPtr. $7f1002 word DodgePtr When player attacks, EntryPtr is set to DodgePtr. $7f1004 long DiePtr When actor dies, EntryPtr is set to DiePtr. $7f1007 byte dummy4 Dummy byte for DiePtr. $7f1008 word CollPtr When actor collides with player, EntryPtr is set to CollPtr if Flags7F2A is so set. $7f100a .... Free100A Through $7f100f, no use known; probably free. $7f1010 .... Scr1010 Through $7f1017, scratch bytes for some COPs, otherwise free. $7f1018 long SnapPtr COP #$43 stores caller here; no other use known. $7f101b byte dummy5 Dummy byte for SnapPtr. $7f101c word Free101C No use known; probably free. $7f101e word ChainDmg When colliding with an actor, damage to deal to it. $7f1020 .... Free1020 Through $7f102f, no use known; probably free.
To save ROM space, actor code uses the COP opcode ($02) and its label as a subroutine caller. Arguments follow the COP label. Example:
02 d0 cop #$D0 ; If game flag a9 00 db $a9, $00 ; $A9 is set to 0, ab cd dw JmpTarget ; go to JmpTarget = $cdab. Else, 02 e0 cop #$E0 ; Die.
Different COPs may return control to the actor after the arguments or at some other address; or may exit (return control to the engine) via pla : pla : rtl, which is equivalent to rtl at the stack level of actor entrancy.
It is always safe to call COP with the processor state as:
m=0, x=0, d=0, i=1 X = ActorID D = ActorID DBR = $81
Some COPs do not require this state.
Most COPs restore non-scratch state upon returning. A few COPs are explicitly state-altering. No COP has a state-altering side effect.
Registers A and Y as well as n,v,z,c are clobbered by the COP handler. Some COPs return useful values in them.
The valid COPs are:
COP | Arguments | Notes |
---|---|---|
cop #$00
|
Generate scaled 16-bit sine @ $7E8900-CFF for HDMA | |
cop #$01
|
dl SrcAddr : db Reg
|
Queue H/DMA to register Reg on available channel |
cop #$02
|
dl SrcAddr : db Reg
|
Queue DMA to register Reg on available channel |
cop #$03
|
db Channel : dl SrcAddr : db Reg
|
Queue H/DMA to Reg using Channel (1-7) |
cop #$04
|
db MusicId
|
Start music |
cop #$05
|
db MusicId
|
Fade out, then start music |
cop #$06
|
db SoundId
|
Play sound, second channel |
cop #$07
|
db SoundId
|
Play sound, first channel |
cop #$08
|
db SoundId1,SoundId2
|
Sound on both channels |
cop #$09
|
db Shift
|
Tempo modifier |
cop #$0A
|
db RawVal
|
Explicit write to $2140 for APU programming |
cop #$0B
|
Set BG tile solidity mask to $Fx (wall) here | |
cop #$0C
|
Set BG tile solidity mask to $0x (clear) here | |
cop #$0D
|
db OffsX,OffsY
|
Set BG tile solidity mask to $Fx, relative |
cop #$0E
|
db OffsX,OffsY
|
Set BG tile solidity mask to $0x, relative |
cop #$0F
|
db AbsX,AbsY
|
Set BG tile solidity mask to $Fx, absolute |
cop #$10
|
db AbsX,AbsY
|
Set BG tile solidity mask to $0x, absolute |
cop #$11
|
Set BG tile solidity mask to $00 on all tiles touched by actor | |
cop #$12
|
db AbsX,AbsY
|
Set BG tile solidity mask to $x0, absolute |
cop #$13
|
dw JmpAddr
|
Branch if this solid mask is not $00 |
cop #$14
|
db OffsX,OffsY : dw JmpAddr
|
Branch if relative solid mask is not $00 |
cop #$15
|
dw JmpAddr
|
Branch if north solid mask is not $00 |
cop #$16
|
dw JmpAddr
|
Branch if south solid mask is not $00 |
cop #$17
|
dw JmpAddr
|
Branch if west solid mask is not $00 |
cop #$18
|
dw JmpAddr
|
Branch if east solid mask is not $00 |
cop #$19
|
db MusicId : dl TextAddr
|
Music and text, similar to cop #$04 + cop #$BF |
cop #$1A
|
db Type : dw JmpAddr
|
Branch if this solid mask is Type |
cop #$1B
|
db Type : dw JmpAddr
|
Branch if north solid mask is Type |
cop #$1C
|
db Type : dw JmpAddr
|
Branch if south solid mask is Type |
cop #$1D
|
db Type : dw JmpAddr
|
Branch if west solid mask is Type |
cop #$1E
|
db Type : dw JmpAddr
|
Branch if east solid mask is Type |
cop #$1F
|
dw JmpAddr
|
Branch if not on gridline |
cop #$20
|
db AcNum,Dist : dw JmpAddr
|
Branch if actor number AcNum from the map's actor list is within Dist |
cop #$21
|
db Dist : dw JmpAddr
|
Branch if player is within Dist |
cop #$22
|
db SpriteId,Speed
|
Basic movement up to $FE pixels; write destination to $7F:18,$7F:1A before calling |
cop #$23
|
RNG, range 0..$FF, returns 8-bit result in A (warning: very expensive call) | |
cop #$24
|
db Max
|
RNG, range 0..Max, returns 8-bit result in $0420 (warning: very expensive call) |
cop #$25
|
db AbsX,AbsY
|
Set new position |
cop #$26
|
db MapNum : dw PosX,PosY
|
Queue map change at end of frame |
cop #$27
|
db Delay
|
If off-screen, wait for Delay frames, then check again |
cop #$28
|
dw PosX,PosY,JmpAddr
|
Branch if player is at Pos |
cop #$29
|
db AcNum : dw PosX,PosY,JmpAddr
|
Branch if actor number AcNum from the map's actor list is at Pos |
cop #$2A
|
dw Dist,WestAddr,HereAddr,EastAddr
|
Branch on whether PlayerX is within Dist, too far west, or too far east |
cop #$2B
|
dw Dist,NorthAddr,HereAddr,SouthAddr
|
Branch on whether PlayerY is within Dist, too far north, or too far south |
cop #$2C
|
dw NearYAddr,NearXAddr
|
Branch on whether Player is nearer in y or x dimension |
cop #$2D
|
Return A=DirToPlayer, 0/1/2 = N/NE/E etc. | |
cop #$2E
|
db OffsX,OffsY
|
Return A=DirToPlayer from relative location |
cop #$2F
|
db DirToPlayer : dw JmpAddr
|
Branch if DirToPlayer is... |
cop #$30
|
db OffsX,OffsY,DirToPlayer : dw JmpAddr
|
Branch if DirToPlayer from relative location is... |
cop #$31
|
dw SouthAddr,NorthAddr,WestAddr,EastAddr
|
Branch on Player's facing direction |
cop #$32
|
db BgChg
|
Stage BG tilemap change (e.g. opening door) from data at $81d3ce + 8*BgChg |
cop #$33
|
Perform staged BG tilemap change | |
cop #$34
|
Castoth door macro, equivalent to cop #$32 : db $7F:24 : cop #$08 : db $0f,$0f : cop #$33 | |
cop #$35
|
Return A=CardinalToPlayer, 0/1/2/3 = N/E/S/W | |
cop #$36
|
Palette handlers: Restart palette bundle | |
cop #$37
|
db PalBundleIndex
|
Palette handlers: Start new palette bundle |
cop #$38
|
db PalBundleIndex,Iters
|
Palette handlers: Start new palette bundle and prepare to loop Iters times |
cop #$39
|
Palette handlers: Advance palette bundle, exit if more palettes remain | |
cop #$3A
|
Palette handlers: Advance or restart palette bundle, exit if more palettes or Iters remain | |
cop #$3B
|
db Param : dl EntryPtr
|
Spawn new thinker running EntryPtr with parameter Param |
cop #$3C
|
dl EntryPtr
|
Spawn new thinker running EntryPtr |
cop #$3D
|
Thinker only: Mark for death after thinker returns this frame | |
cop #$3E
|
dw BtnMask
|
Exit if buttons in BtnMask are not pressed this frame; add 1 to BtnMask to include previous frame |
cop #$3F
|
dw BtnMask
|
Exit if buttons in BtnMask are pressed this frame; add 1 to BtnMask to include previous frame |
cop #$40
|
dw BtnMask,JmpAddr
|
Branch if buttons in BtnMask are pressed this frame; add 1 to BtnMask to include previous frame |
cop #$41
|
dw BtnMask,JmpAddr
|
Branch if buttons in BtnMask are not pressed this frame; add 1 to BtnMask to include previous frame |
cop #$42
|
db AbsX,AbsY,Type
|
Set BG tile solidity mask to Type at absolute location |
cop #$43
|
Snap self to grid | |
cop #$44
|
db XLeft,YUp,XRight,YDown : dw JmpAddr
|
Branch if Player is in signed relative tile area |
cop #$45
|
db XLeft,YTop,XRight,YBot : dw JmpAddr
|
Branch if Player is in absolute tile area |
cop #$46
|
Set position of previous actor (ID in $04) to here | |
cop #$47
|
Set position of next actor (ID in $06) to here | |
cop #$48
|
Return player facing direction, 0/1/2/3 = S/N/W/E | |
cop #$49
|
db PlayerBody : dw JmpAddr
|
Branch if Player's Body is not PlayerBody (0=Will, 1=Freedan, 2=Shadow) |
cop #$4A
|
Utility COP for #$43, probably no ad-hoc use | |
cop #$4B
|
db PosX,PosY,MetatileIndex
|
Draw metatile with collision during VBlank; hangs the actor for ~2 frames |
cop #$4C
|
db Arg1
|
Unknown, used by world map |
cop #$4D
|
db Arg1,Arg2
|
Unknown, used by world map |
cop #$4E
|
db Arg1,Arg2
|
Unknown, used by world map |
cop #$4F
|
dl SrcAddr : dw VramWord,XferSizeB
|
Queue ad hoc DMA of XferSizeB bytes to VRAM at VramWord |
cop #$50
|
dl SrcAddr : db OffsW,PalWord,XferSizeW
|
MVN of XferSizeW words from SrcAddr+2*OffsW to palette stage at $7F0A00+2*PalWord |
cop #$51
|
dl SrcAddr : dw DestAddr
|
Decompress data at SrcAddr into DestAddr in bank $7E |
cop #$52
|
db SpriteId,Speed,MaxTime
|
Stage movement; must write destination to $7F:18,$7F:1A before calling; MaxTime<0 means no limit |
cop #$53
|
Perform movement staged by cop #$52 | |
cop #$54
|
dl Arg
|
Utility function, sets $7F0000,x = Arg and $7F0003,x = $00 |
cop #$55
|
db Spr,New24,New25
|
Resets sprite (as cop #$80) and sets $24 and $25 |
cop #$56
|
Unknown use, advances sprite animation based on global state | |
cop #$57
|
dl OnDeath
|
Set OnDeath pointer |
cop #$58
|
dw OnHit
|
Set OnHit pointer |
cop #$59
|
dw Dodge
|
Set Dodge pointer |
cop #$5A
|
dw OnCollide
|
Set OnCollide pointer |
cop #$5B
|
dw Arg
|
Set $7F:2A = bitwise OR of $7F:2A with Arg |
cop #$5C
|
dw Arg
|
Set $7F:2A = bitwise AND of $7F:2A with Arg |
cop #$5D
|
dw JmpAddr
|
Branch if low-priority sprite and behind wall (i.e. priority bits in $0e are unset and BG tile solidity mask is $xE or $xF) |
cop #$5E
|
dw Arg
|
Set $7F1016,x |
cop #$5F
|
dw BaseAddr : db BytesPerPeriod
|
Initialize scaled sines for HDMA; must write 2*Amplitude to $7F:08 before calling; uses 2KB in bank $7E |
cop #$60
|
db Delay,ScrollLayer
|
Advance scaled sines, offset by PPU scroll value for BG1 (ScrollLayer=0) or BG2 (ScrollLayer=2) |
cop #$61
|
dl SrcAddr : db Reg
|
Queue HDMA to Reg, intended for use with cop #$5F : cop #$60 |
cop #$62
|
db MatchTile : dw JmpAddr
|
Duplicate of cop #$1A |
cop #$63
|
db InitSpeed,NegLogA,GndTilePos
|
Stage gravity |
cop #$64
|
Do gravity (must rtl to move) | |
cop #$65
|
dw PosX,PosY : db Dummy,WMapMoveId
|
Stage world map movement from pixels PosX,PosY using move script pointer indexed at $83ad77; follow with cop #$26 to perform transition |
cop #$66
|
dw PosX,PosY : db WMapOptsId
|
Stage world map choices from pixels PosX,PosY using text box code pointer indexed at $83b401; follow with cop #$26 to perform transition |
cop #$67
|
db Dummy,WMapMoveId
|
As #$65 without setting position; used when already on world map |
cop #$68
|
dw JmpAddr
|
Branch if off-screen |
cop #$69
|
dw Min
|
Exit if $00E4< Min |
cop #$6A
|
dw NewAddr
|
Set CodePtr of Actor06 |
cop #$6B
|
dw TextAddr
|
Text script (alt vers w/no screen refresh) |
cop #$6C
|
db New12,New10
|
Set $7F:12,10 = New12,New10 |
cop #$6D
|
db DiameterSpeed,AngleSpeed
|
Spiral about actor whose ID is stored at $0000 |
cop #$80
|
db Spr
|
Stage new sprite Spr, #$8x = HMirror; or Spr=$FF to reset current animation |
cop #$81
|
db Spr,XMove
|
Stage X movement |
cop #$82
|
db Spr,YMove
|
Stage Y movement |
cop #$83
|
db Spr,XMove,YMove
|
Stage X+Y movement |
cop #$84
|
db Spr,Iters
|
Stage Spr animation loop Iters times |
cop #$85
|
db Spr,Iters,XMove
|
Stage Spr loop and X movement for Iters |
cop #$86
|
db Spr,Iters,YMove
|
Stage Spr loop and Y movement for Iters |
cop #$87
|
db Spr,Iters,XMove,YMove
|
Stage Spr loop and X+Y movement for Iters |
cop #$88
|
dl MetaspriteAddr
|
Set new metasprite data address |
cop #$89
|
Animate and/or move sprite for one iteration, exiting each frame if unfinished | |
cop #$8A
|
Animate and/or move sprite, all staged iterations, exiting each frame if unfinished | |
cop #$8B
|
Animate and/or move one frame only, without exiting | |
cop #$8C
|
db SprFrame
|
Do sprite loops, but continue if at SprFrame |
cop #$8D
|
db Spr
|
Stage Spr as #$80, and update hitbox size if permitted |
cop #$8E
|
db PlayerSpr
|
Stage Player special sprite |
cop #$8F
|
db BodySpr
|
Stage Player normal sprite |
cop #$90
|
db BodySpr,XMove
|
Stage Player X movement |
cop #$91
|
db BodySpr,YMove
|
Stage Player Y movement |
cop #$92
|
db BodySpr,XMove,YMove
|
Stage Player X+Y movement |
cop #$93
|
Duplicate of #$89 | |
cop #$94
|
db BodySpr,XMove,YMove,WallType
|
(unused) As #$92 but would have set WallType for #$96-#$98 |
cop #$95
|
As #$8F but use value at $0000 for BodySpr | |
cop #$96
|
dw BtnMaskTrigger
|
(unused) After #$94, would animate and set a flag if this tile solid mask were WallType |
cop #$97
|
dw BtnMaskTrigger
|
(unused) After #$94, would animate and set a flag if north tile solid mask were WallType |
cop #$98
|
dw BtnMaskTrigger
|
(unused) After #$94, would animate and set a flag if south tile solid mask were WallType |
cop #$99
|
dl SpawnAddr
|
Spawn new actor, running at SpawnAddr, before This in list (ID in $04), returning new ID in Y |
cop #$9A
|
dl SpawnAddr : dw New10
|
As #$99, setting new actor's $10 = New10 |
cop #$9B
|
dl SpawnAddr
|
Spawn new actor, running at SpawnAddr, after This in list (ID in $06), returning new ID in Y |
cop #$9C
|
dl SpawnAddr : dw New10
|
As #$9B, setting new actor's $10 = New10 |
cop #$9D
|
dl SpawnAddr : dw OffsX,OffsY
|
As #$9B, spawning at relative position |
cop #$9E
|
dl SpawnAddr : dw OffsX,OffsY,New10
|
As #$9C and #$9D |
cop #$9F
|
dl SpawnAddr : dw AbsX,AbsY
|
As #$9B, spawning at absolute position |
cop #$A0
|
dl SpawnAddr : dw AbsX,AbsY,New10
|
As #$9C and #$9F |
cop #$A1
|
dl ChildAddr : dw New10
|
As #$9A, marking new actor as Child |
cop #$A2
|
dl ChildAddr : dw New10
|
As #$9C, marking new actor as Child |
cop #$A3
|
dl ChildAddr : dw AbsX,AbsY,New10
|
As #$A0, marking new actor as Child |
cop #$A4
|
dl ChildAddr : db OffsX,OffsY : dw New10
|
As #$9E, with 8-bit pixel offsets, marking new actor as Child |
cop #$A5
|
dl ChildAddr : db OffsX,OffsY : dw New10
|
As #$A4, placing child Last in execution order rather than Next |
cop #$A6
|
dl ChildAddr : db Spr,OffsX,OffsY : dw New10
|
Broken; would have been as #$A5, also setting Child's sprite |
cop #$A7
|
Mark actor for death after next return (and children, if so flagged) | |
cop #$A8
|
Kill Actor04 | |
cop #$A9
|
Kill Actor06 | |
cop #$AA
|
db XMove
|
Stage and save XMove |
cop #$AB
|
db YMove
|
Stage and save YMove |
cop #$AC
|
db XMove,YMove
|
Stage and save X/YMove |
cop #$AD
|
db ForceSW
|
Set/clear forced south/west movement |
cop #$AE
|
db ForceNE
|
Set/clear forced north/east movement |
cop #$AF
|
db ForceNeg
|
Set/clear both, to force negative movement |
cop #$B0
|
db XMoveL,YMoveL
|
Stage and save X/YMove for Last actor |
cop #$B1
|
Load saved movement | |
cop #$B2
|
Set max collision priority flag | |
cop #$B3
|
Set min collision priority flag | |
cop #$B4
|
Clear max collision priority flag | |
cop #$B5
|
Clear min collision priority flag | |
cop #$B6
|
db NewPriority
|
Update sprite priority bits (in $0F) |
cop #$B7
|
db NewPalette
|
Update sprite palette bits (in $0F) |
cop #$B8
|
Toggle HMirror | |
cop #$B9
|
Toggle VMirror | |
cop #$BA
|
Unset HMirror | |
cop #$BB
|
Set HMirror | |
cop #$BC
|
db OffsX,OffsY
|
Set new position immediate |
cop #$BD
|
dl Bg3ScriptAddr
|
Run BG3 script, e.g. drawing status bar or text |
cop #$BE
|
db OptCounts,SkipLines : dw OptionsAddr At OptionsAddr: dw CancelAddr[,ChoiceAddr1,...]
|
Dialogue box options; must print box and text with #$BF then call this |
cop #$BF
|
dw TextAddr
|
Text message |
cop #$C0
|
dw OnInteract
|
Set EntryPtr on player chat/pickup |
cop #$C1
|
Set EntryPtr here, and continue | |
cop #$C2
|
Set EntryPtr here, and exit | |
cop #$C3
|
dl NewPtr : dw Delay
|
Set EntryPtr there, exit, and wait Delay frames |
cop #$C4
|
dl NewPtr
|
Set EntryPtr there, and exit |
cop #$C5
|
Restore SavedPtr | |
cop #$C6
|
dw SavedPtr
|
Set SavedPtr |
cop #$C7
|
dl NewPtr
|
Like JML: set EntryPtr, and continue there |
cop #$C8
|
dw SubPtr
|
Like JSR: set SavedPtr here, EntryPtr at SubPtr, and continue there |
cop #$C9
|
dw SubPtr
|
Like delayed JSR: set SavedPtr here, EntryPtr at SubPtr, and exit |
cop #$CA
|
db Iters
|
Loop from here to next cop #$CB, Iters times |
cop #$CB
|
Loop end, exit if unfinished Iters, otherwise continue | |
cop #$CC
|
db SetFlag
|
Set game flag, range 0..$FF |
cop #$CD
|
dw SetFlagW
|
Set game flag, range 0..$FFFF |
cop #$CE
|
db ClearFlag
|
Clear flag |
cop #$CF
|
dw ClearFlagW
|
Clear flag |
cop #$D0
|
db Flag,Val : dw IfThenAddr
|
Branch if Flag is Val (0/1) |
cop #$D1
|
dw FlagW : db Val : dw IfThenAddr
|
Branch if Flag is Val (0/1) |
cop #$D2
|
db Flag,Val
|
Exit if Flag is not Val (0/1) |
cop #$D3
|
dw FlagW : db Val
|
Exit if Flag is not Val (0/1) |
cop #$D4
|
db AddItemId : dw FullInvAddr
|
Give item, branching if inventory is full |
cop #$D5
|
db RemoveItemId
|
Remove item |
cop #$D6
|
db ItemId : dw HasItemAddr
|
Branch if Player has item |
cop #$D7
|
db ItemId : dw EquippedItemAddr
|
Branch if item is equipped |
cop #$D8
|
Set dungeon-level monster killed flag | |
cop #$D9
|
dw IndexAddr,JmpListAddr
|
Switch-case statement, equivalent to ldx IndexAddr : jmp (JmpListAddr,x) |
cop #$DA
|
db Delay
|
Exit and wait for Delay frames, range 0..$FF |
cop #$DB
|
dw Delay
|
Exit and wait for Delay frames, range 0..$7FFF |
cop #$DC
|
Unclear, conditions on obscure globals | |
cop #$DD
|
Unclear, conditions on obscure globals | |
cop #$DE
|
Unclear, conditions on obscure globals | |
cop #$DF
|
Unclear, conditions on obscure globals | |
cop #$E0
|
Mark for death (with children, if flagged so) and return immediately | |
cop #$E1
|
Restore SavedPtr and set A=#$FFFF | |
cop #$E2
|
dl NewPtr
|
Set EntryPtr, but continue here this frame |
Flags are stored in $10, $12, and $7F:2A:
$10 & #$8000 Is Player $10 & #$4000 Offscreen (engine temp flag) $10 & #$2000 Disable rendering and collisions $10 & #$1000 Interactable/chattable/useable $10 & #$0800 Continue acting during dialogue $10 & #$0400 Is weapon of Player (e.g. Friar) $10 & #$0200 Disable collisions with weapons $10 & #$0100 Disable collisions with Player $10 & #$0080 Is hurt (engine temp flag) $10 & #$0040 Is dying (engine temp flag) $10 & #$0020 Is item pickup, e.g. DP (but other obscure uses too) $10 & #$0010 Invulnerable; make clanging sound on hit $10 & #$0008 If moving via COP #$8x, collide with walls $10 & #$0004 Is colliding with a wall (engine temp flag) $10 & #$0002 Maximum OAM priority $10 & #$0001 Minimum OAM priority $12 & #$8000 ? $12 & #$4000 If moving via COP #$8x, force movement to be west/south $12 & #$2000 If moving via COP #$8x, force movement to be east/north $12 & #$1000 ? $12 & #$0800 ? $12 & #$0400 ? $12 & #$0200 ? $12 & #$0100 Constant hitbox (even if sprite changes) $12 & #$0080 Cut bottom 8px of hitbox (to seem of higher elevation) $12 & #$0040 Remove children on death $12 & #$0020 Hide HP $12 & #$0010 Disable recoil on damage $12 & #$0008 ? $12 & #$0004 ? $12 & #$0002 Persistent h-mirror OAM flag (resist being changed by COPs) $12 & #$0001 Disable stun on damage $7F:2A & #$0080 Exclude from monster counter on radar $7F:2A & #$0010 Enable CollPtr $7F:2A & #$0002 Forced movement (e.g. recoil or cop #$22) in progress (engine temp flag) $7F:2A & #$xxxx Others unknown
Thinker code
To run code during VBlank, at end-of-frame, or while other processing is paused (e.g. during music loads), use a thinker. These are like lightweight, no-sprite actors. Most concepts about actor code apply to thinkers.
In thinker memory, if flag #$0004 in $7f000e,x is set, code will run after actor processing. If clear, code will run after the NMI handler. The NMI handler finishes during VBlank (unless you seriously abuse DMA), so thinkers can e.g. do ad hoc updates to CGRAM or MMIO registers.
Key points:
- Thinker memory is blocks of $10 bytes, instead of $30.
- Direct page $00 - $0d have the same meaning as for actors. $0e/f as well as $7f:0e/f contain different flags. Other memory is free.
- Don't use COPs that affect properties the thinker doesn't have, e.g. position, sprite, physics. Probably the only useful COPs are #$36 - #$3D and #$C1 - #$DB.
- To die, a thinker must call COP #$3D and subsequently rtl.
Sprite data format
VRAM characters for most actors are loaded by map header $03. It is possible to load VRAM ad hoc, e.g. with cop #$4f, but ad hoc changes are permanently lost if the player opens the inventory screen.
VRAM characters for the player are loaded from pointers in a table at $81d971:
{ .long ptrAnimationList00 ; Will .long ptrCharacterData00 ; Will .long ptrAnimationList01 ; Freedan .long ptrCharacterData01 ; Freedan .long ptrAnimationList02 ; Shadow .long ptrCharacterData02 ; Shadow ; etc. for transformations, abilities }
Actor sprites/animations are chosen from an AnimationList a.k.a. SpriteSet. Actor memory $7f0008,x is a pointer to an AnimationList. For monsters the AnimationList is map-specific, always at $7e4000, and loaded by map header $10.
The AnimationList/SpriteSet format is:
AnimationList: { .word ptrAnimationData00 ; e.g. Will facing south, or Viper fluttering .word ptrAnimationData01 ; e.g. Will facing north, or Viper fast-fluttering ; etc. } AnimationData: { .word frameDuration00 ; e.g. $77 frames of... .word ptrMetasprite00 ; e.g. Will staring, followed by .word frameDuration01 ; e.g. $04 frames of... .word ptrMetasprite01 ; e.g. Will blinking ; etc. .word $FFFF } Metasprite: { .byte xOffset ; Sprite and hurtbox start at ActorPos - Offset. .byte xOffsetMirror ; Hurtbox size is Offset + OffsetMirror. .byte yOffset ; Offset/OffsetMirror are swapped if actor is reflected. .byte yOffsetMirror ; Notes on customizing the hurtbox are below. .byte xRecoilHitboxOffset ; RecoilHitbox starts at ActorPos + Offset. .byte yRecoilHitboxOffset ; Used when actor is recoiling from .byte xRecoilHitboxSizeInTiles ; damage, for wall collisions .byte yRecoilHitboxSizeInTiles ; and bumping other actors. .byte xHostileHitboxOffset ; HostileHitbox starts at ActorPos + Offset. .byte xHostileHitboxSize ; Used when damaging other actors. .byte yHostileHitboxOffset ; Offsets in the Metasprite section .byte yHostileHitboxSize ; are signed, sizes are unsigned. .byte numSprites { Sprite00 } ; e.g. a head { Sprite01 } ; e.g. an arm ; etc. } Sprite: { .byte isLargeSpriteBool ; 8x8 small, 16x16 large .byte xOffset ; These offsets are unsigned. .byte xOffsetMirror ; Mirror offsets are used if the actor is reflected. .byte yOffset .byte yOffsetMirror .word propertiesAndAddress ; i.e. OAM data, vhoopppccccccccc }
For Player sprites, character data is pushed to VRAM from ROM CharacterData + ((propertiesAndAddress & $01FF) << 5). For all other sprites, propertiesAndAddress is the sprite's literal OAM data, so the Address portion is a VRAM word-address.
The sprite and hurtbox extend east/south from ActorPos - x/yOffset (or x/yOffsetMirror if reflected). To manually set hurtbox bounds, set flag #$0100 in actor memory $12 and manually write values for xOffset/xOffsetMirror/yOffset/yOffsetMirror at $20/$21/$22/$23.
Palette bundle data format
Ad hoc palette changes (i.e. CGRAM updates) are conventionally performed using scripted palette bundles. There are $80 palette bundles, all in bank $16 and indexed by pointers at $168000. A palette bundle contains one or more palette sequences. Sequences contains blocks of palette data that are sequentially written to the same region of CGRAM (via cop #$39
). Sequences are not interruptible, but after cycling through the entire sequence, the calling code may restart the same sequence (via cop #$36
) or switch to a different sequence in the bundle (via lda #SequenceNum : sta $0e
).
The indexed pointers in bank $16 point to palette bundle headers, which contain 6 bytes for every sequence in the bundle:
PaletteSequenceHeader: { .byte numPalettes .word dataSourceAddress .byte cgramTargetWord ; or $00 to write COLDATA instead of CGRAM .byte numBytesMinusOne ; or $02 to write COLDATA instead of CGRAM .byte delayFrames ; until next CGRAM write }
followed by a terminal $00.
Palette source data within a bundle can overlap. This is a common design for e.g. wave or flame effects, where a small number of colors are rolled cyclically through CGRAM.
A thinker-type actor is usually used to handle palette bundles; this has the advantage that (due to how thinker initialization is coded) the palette bundle can be efficiently spawned using cop #$3B
with Param
= the desired palette bundle index. Common palette bundle handler actors are $80b519 which runs a bundle's first sequence exactly once then dies, and $80b520 which runs a bundle's first sequence repeatedly forever.
Music data format
The music engine is based on N-SPC, with some modifications to streamline data loads. There are $3C BRR samples, stored consecutively starting at address $C50000, and wrapping around to the next bank at $xx8000. Each sample is preceded by 2 bytes indicating its length.
Samples $00 - $0d are used to construct sound effects; the remaining samples are for music. The following table describes the samples and the information that's needed to put them in a song:
SampleID | Approximate sound | Used in Areas | InstrumentDefinition | SampleLength | LoopStartOffset |
---|---|---|---|---|---|
$00 | SFX | N/A | $FF,$E0,$B8,$03,$90 | $065D | $0009 |
$01 | SFX | N/A | $8F,$E0,$B8,$04,$00 | $0063 | $003F |
$02 | SFX | N/A | $8F,$F4,$B8,$03,$C0 | $09E1 | $09E1 |
$03 | SFX | N/A | $8F,$E9,$B8,$06,$F0 | $0051 | $0012 |
$04 | SFX | N/A | $FF,$E0,$B8,$04,$80 | $034E | $034E |
$05 | SFX | N/A | $FF,$E0,$B8,$02,$80 | $022E | $022E |
$06 | SFX | N/A | $8F,$F6,$B8,$0F,$70 | $0492 | $0264 |
$07 | SFX | N/A | $8F,$E0,$B8,$04,$00 | $0063 | $003F |
$08 | SFX | N/A | $FF,$E0,$B8,$03,$D0 | $0585 | $0585 |
$09 | SFX | N/A | $FF,$E0,$B8,$03,$D0 | $0585 | $0585 |
$0A | SFX | N/A | $FF,$E4,$B8,$0A,$E0 | $0DD1 | $0438 |
$0B | SFX | N/A | $8F,$EB,$B8,$01,$00 | $001B | $0012 |
$0C | SFX | N/A | $FF,$8E,$B8,$01,$F0 | $0105 | $00F3 |
$0D | SFX | N/A | $FF,$91,$B8,$03,$D0 | $0585 | $0585 |
$0E | Electric guitar 1 | Raft, City | $FF,$E0,$B8,$0D,$A0 | $12EA | $0480 |
$0F | Strings 1 | Castle, Inca, City | $FB,$E0,$B8,$04,$90 | $19BC | $0009 |
$10 | Flute 1 | Raft, City, Great Wall | $FF,$88,$B8,$03,$F0 | $05A9 | $0585 |
$11 | Glockenspiel | Village, Dreams, Raft, City | $FF,$8E,$B8,$03,$F0 | $02BE | $029A |
$12 | Snare 1 | City | $FF,$E0,$B8,$05,$50 | $03DE | $03DE |
$13 | Jingle bell | City, Space flight chorus | $FF,$E0,$B8,$05,$B0 | $13CB | $13CB |
$14 | Acoustic guitar 1 | Village | $FF,$70,$B8,$0D,$00 | $0804 | $078F |
$15 | Pan flute | Silence, Village, Spooky, Inca, Flute songs | $F8,$8A,$B8,$08,$F0 | $0B37 | $0AE6 |
$16 | Woodblock 1 | Village | $FF,$E0,$B8,$07,$A0 | $07B3 | $07B3 |
$17 | Bongo | Village | $FF,$E0,$B8,$07,$A0 | $07B3 | $07B3 |
$18 | Chirp 1 | Village | $FF,$E0,$B8,$02,$A0 | $0747 | $0747 |
$19 | Chirp 2 | Village | $FF,$E0,$B8,$02,$80 | $0747 | $0747 |
$1A | Choir 1 | Spooky, Dreams | $F8,$E0,$B8,$02,$F0 | $141C | $0009 |
$1B | Pizzicato strings 1 | Spooky, Raft, Sky Garden, Mu, Angkor Wat, Pyramid | $BF,$6C,$B8,$04,$F0 | $046E | $0441 |
$1C | Strings 2 | Castle, MinorDungeon, Spooky, Inca, Sky Garden, Mu, Great Wall, Angkor Wat, Pyramid, Boss fight, Item fanfare, Comet | $F9,$E0,$B8,$08,$F0 | $10A1 | $039F |
$1D | Tom-tom 1 | Castle, MinorDungeon, Spooky, Inca, Sky Garden, Mu, Great Wall, Angkor Wat, Pyramid, Boss fight, Dark Space, Item fanfare, Comet | $FF,$F1,$B8,$03,$D0 | $15F9 | $15F9 |
$1E | Synth brass 1 | Castle | $FF,$A8,$B8,$03,$F0 | $0786 | $0762 |
$1F | Horn 1 | Castle, Sky Garden, Item fanfare, Comet | $FF,$A8,$B8,$04,$70 | $0615 | $0573 |
$20 | Strings 3 | MinorDungeon, Dreams, Raft, Sky Garden, Mu, Great Wall, Angkor Wat, Pyramid, Boss fight, Item fanfare, Comet | $FC,$E0,$B8,$04,$70 | $1E96 | $019E |
$21 | Trumpet 1 | MinorDungeon, Sky Garden, Mu, Pyramid | $AF,$88,$B8,$02,$F0 | $044A | $042F |
$22 | Piccolo | MinorDungeon, Dark Space, World map | $F8,$A8,$B8,$03,$F0 | $04E3 | $04BF |
$23 | Muted trumpet 1 | Inca, Great Wall, Angkor Wat, Pyramid | $FC,$AA,$B8,$05,$F0 | $042F | $03F9 |
$24 | Orchestra hit 1 | Inca, Mu, Angkor Wat | $FF,$E0,$B8,$02,$80 | $0D14 | $0D14 |
$25 | Koto | Great Wall | $CF,$52,$B8,$04,$70 | $040B | $03BA |
$26 | Orchestra hit 2 | Pyramid | $FF,$E0,$B8,$01,$F0 | $0B01 | $0B01 |
$27 | Choir 2 | Space flight chorus | $F9,$A8,$B8,$03,$50 | $2EB9 | $0009 |
$28 | Electric piano | Space flight chorus, Dark Space | $FF,$8C,$B8,$03,$F0 | $095A | $0936 |
$29 | Trumpet 2 | Boss fight, Comet | $AF,$88,$B8,$02,$F0 | $0465 | $044A |
$2A | Piano | Dreams | $FF,$8C,$B8,$04,$70 | $0DD1 | $0D80 |
$2B | Bass | World map | $9F,$68,$B8,$19,$00 | $049B | $03BA |
$2C | Acoustic guitar 2 | World map | $AF,$4C,$B8,$09,$00 | $0E10 | $0615 |
$2D | Cymbal 1 | Credits | $FF,$8D,$B8,$03,$D0 | $20FA | $20FA |
$2E | Electric guitar 2 | Credits, Unused fanfare | $F9,$E0,$B8,$08,$F0 | $10A1 | $039F |
$2F | Tom-tom 2 | Credits, Unused fanfare | $FF,$F1,$B8,$03,$D0 | $15F9 | $15F9 |
$30 | Cymbal 2 | Credits | $FF,$8D,$B8,$03,$D0 | $20FA | $20FA |
$31 | Snare 2 | Credits, Unused fanfare | $FF,$E0,$B8,$05,$50 | $03DE | $03DE |
$32 | Horn 2 | Credits, Unused fanfare | $FF,$A8,$B8,$04,$70 | $0615 | $0573 |
$33 | Synth brass 2 | Credits, Unused fanfare | $FF,$A8,$B8,$03,$F0 | $0786 | $0762 |
$34 | Muted Trumpet 2 | Credits | $FC,$AB,$B8,$05,$F0 | $042F | $03F9 |
$35 | Strings 4 | Credits, Unused fanfare | $FC,$E0,$B8,$04,$70 | $1E96 | $019E |
$36 | Pizzicato strings 2 | Credits | $BF,$6C,$B8,$04,$F0 | $046E | $0441 |
$37 | Flute 2 | Credits | $FF,$88,$B8,$03,$F0 | $05A9 | $0585 |
$38 | Choir 3 | Dark Space | $FF,$CA,$B8,$04,$70 | $138C | $0E46 |
$39 | Woodblock 2 | Dark Space | $FF,$FE,$B8,$07,$A0 | $026D | $026D |
$3A | Waterdrop | Dark Space | $FF,$E0,$B8,$03,$D0 | $0585 | $0585 |
$3B | Squawk | South Cape | $FF,$E0,$B8,$02,$40 | $09EA | $09EA |
Music is loaded via map header $11 with an address, or via cop #$04/#$05 with an index; the COP argument is 1-indexed and pulls from the table of music addresses at $81cba6 (so cop #$04 : db $01 selects the first song, not the second).
Music data contains two segments. The first segment consists of any number of blocks with the following structure:
dw PayloadSize dw SpcRamTargetAddr db Payload
PayloadSize is the number of bytes in Payload, and the bytes of Payload will be written to ARAM at SpcRamTargetAddr. To follow IOG convention, music data for a song using InstrumentCount instruments should contain the following block writes in the following order:
- At
$1254
, write 6 bytes per instrument in the song: one for the instrument number (starting from $0E), then 5 to define the ADSR envelope, gain, and pitch. The 5 definition bytes for each instrument are given in the table above. Regardless of which instruments you're actually using, the first instrument number is $0E, the second is $0F, etc. - At
$1300
, write these literal $0018 bytes:$32,$65,$7F,$98,$B2,$CB,$E5,$FC,$0A,$19,$28,$3C,$50,$64,$7D,$96,$AA,$B9,$C8,$D4,$E1,$EB,$F5,$FF
- At
$5532
, write the N-SPC song data. By IOG convention the song should contain a pickup segment, which plays once, followed by a looping segment, which plays forever. Songs should generally only use 6 channels, because the final two channels are used for sound effects. The basic conventional structure for this payload is as follows:
dw $5534 ; Pointer to start of song dw $553e ; Pointer to list of pointers to each channel's pickup segment dw $554e ; Pointer to list of pointers to each channel's looping segment dw $00ff : dw $5536 ; Return to start of looping segments when looping segment ends dw $0000 ; End song (unreachable due to the above loop jump) dw Track0Intro,Track1Intro,Track2Intro,Track3Intro,Track4Intro,Track5Intro,Track6Intro,Track7Intro dw Track0LoopStart,Track1LoopStart,Track2LoopStart,Track3LoopStart,Track4LoopStart,Track5LoopStart,Track6LoopStart,Track7LoopStart ; [For unused tracks, set TrackNIntro = $0000 and TrackNLoopStart = $0000.] Track0Intro: ; <track data> db $00 Track1Intro: ; <track data> db $00 ; etc.
- At
$1038
, write 4 bytes per instrument, as two word-size pointers. The first pointer points to the start of the instrument's sample data in ARAM, and the second points to its loop point. This populates the DSP DIR register. By IOG convention, sample data follows song data, so the pointers can be calculated as follows:- 1st instrument, 1st pointer, i.e. address of 1st instrument sample = $5532 + song length
- 1st instrument, 2nd pointer, i.e. 1st instrument loop point = $5532 + song length + 1st instrument's LoopStartOffset from table above
- Nth instrument, 1st pointer = (N-1)th instrument, 1st pointer + (N-1)th instrument's SampleLength from table above
- Nth instrument, 2nd pointer = Nth instrument, 1st pointer + Nth instrument's LoopStartOffset from table above
- At
$0ffc
, write the literal word $5532 - At
$0ffe
, write a 2-byte pointer to the byte after the last instrument sample, which can be calculated as $5532 + song length + sum of all SampleLengths of loaded instruments - At
$0fe0
, write the literal bytes $0E,$0F,...,($0E + InstrumentCount - 1)
Following all of those block writes comes the second segment of music data, which consists of:
dw $0000 dw SpcRamSampleAddr db InstrumentCount db InstrumentId1,InstrumentId2,...
InstrumentCount is the number of instrument samples to load for this song, and the InstrumentIds are their IDs. The samples for those instruments will be written to ARAM at SpcRamSampleAddr, which should be set immediately after the song data, i.e. at $5532 + song length.
The above convention arises because the sound effect samples (IDs $00 - $0d) are always in ARAM, while music samples are loaded only if the active song uses them—we load music sample data and metadata into pre-existing structures that already contain sound effect data and metadata. For example, the list of all 6-byte instrument definitions starts at $1200 in ARAM. There are $0e static entries for the sound effect instruments, totalling $54 bytes; so definitions of additional instruments must start at $1254. From the SPC's perspective, the first instrument we load and add to the list is instrument $0E, the next is $0F, etc., because the SPC doesn't know how the samples are ordered or numbered in ROM.
Drawing text and BG3 tiles
For drawing text and dialogue boxes, and otherwise painting the BG3 layer (e.g. the HUD and radar), the game has two different engines. One engine is used for general dialogue and interaction text; it draws double-height characters (8px by 16px), and has features that make it easier to program dialogue. The other engine is more general-purpose, being used for inventory screen text as well as most non-text elements like the HUD and radar; it draws single-height characters (8px by 8px), and has features that make it easier to draw arbitrary tile patterns.
Both engines have many features in common.
Instructions for painting BG3 are provided to the engines as a byte script. Some bytes represent "opcodes" that change engine parameters, like setting the screen position where tiles will be drawn. All other bytes represent tiles ("characters") for the engine to draw, by indexing the tile graphic address in VRAM. (When writing text, this is why the encoding is not ASCII: the bytes correspond to VRAM addresses, not Latin letters.)
An internal cursor stores the screen position where a character will be drawn. Drawing a character advances the cursor to the right, and from the right edge of the screen, the cursor advances to the left edge and lower by one tile. The internal cursor also has a starting position, set by some opcodes; this is important when drawing text. A "newline" opcode generally behaves intuitively, moving the cursor horizontally to line up with its starting position and then down one character. (The actual behavior is to keep a count of the number of newlines processed, and move the cursor down by that many characters. This detail doesn't matter in any normal text-writing scenario.)
Scripts may "include" other scripts via an opcode. This is mostly equivalent to copying the target script into the calling script. However, opcodes that reference word-size addresses will use the bank of the target script, not the parent script. (There is a special case related to the terminating character of a dialogue script, detailed in that section.)
A script ends with a terminating character, which returns processing to the calling code or script.
Dialogue
A dialogue script is called by cop #$BF during gameplay, or by cop #$6B on the main menu; the former routine runs extra code to prepare the game world to be frozen during the dialogue. The entry point of the script has to be in the bank of the caller, but via opcode $CD, nearly all of the script can be stored in any bank.
In addition to the common features, the dialogue engine supports a dialogue box. A dialogue box is drawn by opcodes, then the cursor's starting position is set at the first character position inside the box. A new-page opcode clears the contents of the dialogue box and returns the cursor to the starting position. If the cursor is on the last character line of the dialogue box, a newline scrolls the entire text upward by one line instead of advancing the cursor below of the box; the top line of text is cleared in this case.
It is possible to draw more than one dialogue box on the screen. However, only the most-recently-created box can be written in, interacted with, or even deleted; all previous boxes and text remain as fixed tiles. To remove previous boxes, you have to draw a new box over the old one and then delete the new box. (The Japanese release, however, supported swapping between two active dialogue boxes.)
Dialogue scripts generally halt game processing, freezing the player etc. until the text is fully drawn and, if the script requires player input, the player has provided that input. In actor memory, flag #$0800 in address $10 causes actor code to continue running through a dialogue box.
There are two terminating opcodes for dialogue scripts. Byte $C0 requires the player to press a button, then clears the screen and returns control to the caller; this is the normal way to end dialogue. Byte $CA immediately returns control to the caller, without awaiting input or clearing the screen. Therefore, scripts that will be included should generally end in $CA, and top-level scripts should generally end in $C0. A notable exception is that dialogue scripts for multiple-choice prompts (i.e. menus) should use the $CA terminator, so that the text remains on-screen during the menu interaction.
All byte values $C0 and above are treated as opcodes, though bytes greater than $D8 have no defined code and will probably crash the game if used. The dialogue box opcodes are:
Opcode | Arguments | Notes |
---|---|---|
C0
|
None
|
Pauses awaiting input, then clears the screen and returns. The normal way to end a dialogue. Equivalent to $D0 + $C8 + $CA but without the flickering arrow. |
C1
|
db PosX, PosY
|
Creates a dialogue box with its upper-left corner at PosX,PosY and sets the cursor's initial position. The minimum is 1,1. |
C2
|
db TemplateId
|
Includes a template script. These template scripts are indexed by pointers at $81ca95 and set up the box and text in commonly-used ways. Specifically: templates 0-6 set the color and sound of Will/Kara/Lilly/Erik/Lance/Seth/Neil, and 7 sets a red color; templates 8/9/10/11 create a box near the top of the screen of height 1/2/3/4, and templates 12/13/14/15 do likewise near the bottom of the screen; 16 ($10) is a duplicate of 15 ($0F), both of which produce the default dialogue box size and position; $11 creates a wide 3-line box at the bottom of the screen headered with "Where do you go?"; and $12-$15 set the colors of Will/Kara/Lilly/Lance. |
C3
|
db NewBG3PaletteId
|
Changes the palette of subsequent text. These are the seven 4-color BG3 palettes. Guaranteed usable options are 0 for the default blue, and 4 for the alternative that's set by code $D4. In principle, any of 0-7 can be used, but palettes other than 0 and 4 are reserved for the HUD and radar. |
C4
|
None
|
Not usable; just hangs the game. (The functionality of this code was not ported from the Japanese release.) |
C5
|
dw AddressOfPointerList, AddressContainingIndex
|
Indexed include; given a list of pointers to text scripts, the value at AddressContainingIndex indexes the script to include. In other words, in the same bank as this code, includes the script at the address whose value is at AddressOfPointerList + twice the value at AddressContainingIndex. |
C6
|
dw NumDigits, AddressContainingNumber
|
Prints the hexadecimal numeric value at a memory address. The least significant NumDigits digits are drawn, omitting leading zeroes. To print the value in base 10, store it as BCD. |
C7
|
db SizeX, SizeY
|
Sets box size. Sizes the printable area of the text box to be "SizeX" letters wide and "SizeY" lines high. The border of the box is sized to fit around the printable area. |
C8
|
None
|
Deletes the dialogue box, clearing all of its BG3 tiles. |
C9
|
db Delay
|
Pauses printing for the specified number of frames. |
CA
|
None
|
Ends the script, returning control to the caller. This is the normal way to end an included script, as well as top-level scripts that shouldn't clear the screen, such as menus. |
CB
|
None
|
Starts a new line of text, as a carriage-return + line-feed. |
CC
|
db SkipSize
|
Advances the cursor by SkipSize/2 letters. |
CD
|
dl IncludeAddr
|
Long-include. Includes the script at the long address IncludeAddr. |
CE
|
None
|
Page-advance. Clears the dialogue box and returns the cursor to its initial position. |
CF
|
None
|
Pauses awaiting input from the player, then clears the dialogue box and continues. Equivalent to $D0 + $CE. |
D0
|
None
|
Pauses awaiting input from the player. |
D1
|
dw JumpAddr
|
Jumps to a different address in this bank. This is a true jump, not an include. |
D2
|
db SoundId
|
Sets the sound that will be played when a character (other than whitespace) is printed. |
D3
|
None
|
Creates the default dialogue box: four lines of 26 characters, near the bottom of the screen, blue text, no per-character delay, and a neutral sound. |
D4
|
db PaletteWordId : dw NewValue
|
Sets any of the $100 palette colors to NewValue. The main use for this is to set words $11 and $12, which are in BG3 palette 4. |
D5
|
db Delay
|
Sets a frame delay of 1 frame plus the argument before the printing of each character. To return to drawing all text simultaneously, clear the screen and create a new box. |
D6
|
db DictEntryIndex
|
Include text from the dictionary with entries indexed at $81eba8. |
D7
|
db DictEntryIndex
|
Include text from the dictionary with entries indexed at $81f54d. |
D8
|
db Char1,Char2,...,$00
|
Escape byte. Parses and prints subsequent bytes as characters even if they're in the opcode range. This is used for printing the hieroglyph texts, which occupy characters $C0 - $CB. The escaped sequence is terminated by a $00 byte. |
Byte values below $C0 print a double-height "character". The byte indexes a BG3 VRAM tile, which becomes the upper half of the printed character. The lower half is the tile $10 positions after the upper tile. Because half of the bytes index the lower half of a letter, and some of the VRAM space is blank or used for other purposes, fewer than half of the bytes encode a printable character. They are:
Byte | Character |
---|---|
0D | ? |
0E | ' |
20 | 0 |
21 | 1 |
22 | 2 |
23 | 3 |
24 | 4 |
25 | 5 |
26 | 6 |
27 | 7 |
28 | 8 |
29 | 9 |
2A | . |
2B | , |
2C | ► |
2D | “ |
2E | ” |
2F | : |
40 | A |
41 | B |
42 | C |
43 | D |
44 | E |
45 | F |
46 | G |
47 | H |
48 | I |
49 | J |
4A | K |
4B | L |
4C | M |
4D | N |
4E | O |
4F | ! |
60 | P |
61 | Q |
62 | R |
63 | S |
64 | T |
65 | U |
66 | V |
67 | W |
68 | X |
69 | Y |
6A | Z |
6B | / |
6C | * |
6D | - |
6E | ( |
6F | ) |
80 | a |
81 | b |
82 | c |
83 | d |
84 | e |
85 | f |
86 | g |
87 | h |
88 | i |
89 | j |
8A | k |
8B | l |
8C | m |
8D | n |
8E | o |
A0 | p |
A1 | q |
A2 | r |
A3 | s |
A4 | t |
A5 | u |
A6 | v |
A7 | w |
A8 | x |
A9 | y |
AA | z |
AC | Space; prints silently |
The dictionary entries indexed by opcodes $D6 and $D7 all end with a blank space, so you don't need to add a space after including one. The dictionaries were apparently developed for the beta translation and not fully updated for the final translation: note the use of "Morris", "Rob", and "Eric", and the spelling of "Itorie"; "Kara" and "Karen" both are present; "Will" is present, but not "Tim".
These are the dictionary entries for opcode $D6:
Byte | Text |
---|---|
00 | Attack |
01 | Angel |
02 | After |
03 | Aura |
04 | Ankor |
05 | Black |
06 | Bill: |
07 | Crystal |
08 | City |
09 | Come |
0A | Condor's |
0B | Change |
0C | Dark |
0D | Don't |
0E | Diamond |
0F | Drifting, |
10 | Eric: |
11 | Edward |
12 | Even |
13 | Elder: |
14 | Earth |
15 | Freedan's |
16 | Great |
17 | Grandma |
18 | Good. |
19 | Gold |
1A | Grandpa |
1B | Hieroglyph |
1C | Hey, |
1D | It's |
1E | Inca |
1F | I'll |
20 | I've |
21 | Itorie |
22 | Jewels |
23 | Jewels! |
24 | Just |
25 | Kara: |
26 | Kara |
27 | King |
28 | Knight |
29 | Karen's |
2A | Lilly: |
2B | Let's |
2C | Lilly |
2D | Lola's |
2E | Lola: |
2F | Mystery |
30 | Maybe |
31 | Moon |
32 | Man: |
33 | Morris's |
34 | Melody |
35 | Morris: |
36 | Neil: |
37 | Neil's |
38 | Only |
39 | Once |
3A | Oink |
3B | Please |
3C | Psycho |
3D | People |
3E | Prayer |
3F | Pyramid |
40 | Purification |
41 | Plate |
42 | Quit |
43 | Rob: |
44 | Rolek |
45 | Soldier: |
46 | Strange |
47 | South |
48 | Statue |
49 | Somehow |
4A | Sometimes |
4B | Something |
4C | Shadow's |
4D | Someone |
4E | This |
4F | Will: |
50 | There's |
51 | Will's |
52 | There |
53 | That's |
54 | Tower |
55 | They |
56 | Then |
57 | Trip |
58 | Thank |
59 | Take |
5A | Will. |
5B | That |
5C | Will, |
5D | They're |
5E | These |
5F | Vampire |
60 | Village. |
61 | When |
62 | What |
63 | Well, |
64 | What's |
65 | Where |
66 | Woman: |
67 | You've |
68 | Your |
69 | You're |
6A | Yes, |
6B | about |
6C | anything |
6D | around |
6E | another |
6F | ancient |
70 | been |
71 | become |
72 | body |
73 | back |
74 | before |
75 | brought |
76 | beautiful |
77 | being |
78 | can't |
79 | come |
7A | could |
7B | comet |
7C | company |
7D | children |
7E | constellation |
7F | changed |
80 | came |
81 | coming |
82 | don't |
83 | didn't |
84 | doesn't |
85 | distant |
86 | different |
87 | demons |
88 | destroy |
89 | everyone |
8A | explorer |
8B | everyone's |
8C | enemies |
8D | exposed |
8E | from |
8F | found |
90 | find |
91 | feel |
92 | father's |
93 | going |
94 | good |
95 | great |
96 | ground |
97 | give |
98 | have |
99 | heard |
9A | human |
9B | hear |
9C | huge |
9D | happened |
9E | hieroglyph |
9F | it's |
A0 | inventory |
A1 | into |
A2 | inside |
A3 | just |
A4 | know |
A5 | like |
A6 | long |
A7 | little |
A8 | light |
A9 | look |
AA | looks |
AB | looking |
AC | leave |
AD | labor |
AE | left |
AF | live |
B0 | life |
B1 | living |
B2 | must |
B3 | made |
B4 | melody |
B5 | move |
B6 | many |
B7 | more |
B8 | matter |
B9 | nothing |
BA | need |
BB | never |
BC | next |
BD | other |
BE | over |
BF | outside |
C0 | original |
C1 | people |
C2 | power |
C3 | present. |
C4 | playing |
C5 | raised |
C6 | right-hand |
C7 | strange |
C8 | something |
C9 | statue |
CA | should |
CB | started |
CC | seems |
CD | same |
CE | such |
CF | someone |
D0 | some |
D1 | save |
D2 | statues |
D3 | still |
D4 | said |
D5 | stands |
D6 | this |
D7 | that |
D8 | thought |
D9 | there |
DA | think |
DB | there's |
DC | through |
DD | tried |
DE | terrible |
DF | time |
E0 | things |
E1 | their |
E2 | town |
E3 | thing |
E4 | these |
E5 | temple |
E6 | them |
E7 | take |
E8 | turned, |
E9 | understand |
EA | under |
EB | underwater |
EC | village |
ED | very |
EE | voice |
EF | will |
F0 | with |
F1 | want |
F2 | were |
F3 | would |
F4 | where |
F5 | world |
F6 | when |
F7 | what |
F8 | without |
F9 | wonder |
FA | won't |
FB | wouldn't |
FC | wanted |
FD | waiting |
FE | your |
FF | you're |
These are the dictionary entries for opcode $D7:
Byte | Text |
---|---|
00 | 1994 |
01 | Actually, |
02 | Button |
03 | Buttons |
04 | Back |
05 | Child: |
06 | Defeating |
07 | Didn't |
08 | Desert |
09 | Erasquez: |
0A | Elder |
0B | Earth's |
0C | Eric's |
0D | Everybody |
0E | Flute: |
0F | First |
10 | Firebird, |
11 | From |
12 | Gaia, |
13 | Gorgon |
14 | Have |
15 | Hey! |
16 | Istar's |
17 | Ishtar's |
18 | Lilly's |
19 | Larai |
1A | Looking |
1B | Main |
1C | Man's |
1D | Memory |
1E | Mountain |
1F | Morris |
20 | Many |
21 | Native |
22 | Native's |
23 | Neil |
24 | Naska |
25 | Power |
26 | Prison |
27 | Play |
28 | Panther's |
29 | Rob's |
2A | Recently |
2B | Restores |
2C | Right |
2D | Russian |
2E | Rofsky |
2F | Rearrange |
30 | Special |
31 | Spin |
32 | Seaside |
33 | She's |
34 | Shall |
35 | Sorry |
36 | Select |
37 | Show |
38 | Soon |
39 | Will! |
3A | Will... |
3B | Tell |
3C | Uses |
3D | Wind |
3E | We'll |
3F | We're |
40 | With |
41 | We've |
42 | Wait |
43 | Watermia. |
44 | always |
45 | approaching |
46 | animals |
47 | almost |
48 | also |
49 | anything. |
4A | abolition |
4B | breath |
4C | birthday |
4D | best |
4E | bring |
4F | bouquet |
50 | better |
51 | behind |
52 | change |
53 | castle |
54 | called |
55 | comet's |
56 | condition. |
57 | care |
58 | door |
59 | destroyed |
5A | disappeared |
5B | discovered |
5C | dark |
5D | ever |
5E | elevator |
5F | explorer. |
60 | eyes |
61 | first |
62 | fish |
63 | followed |
64 | fountain |
65 | floor |
66 | finally |
67 | father |
68 | flower |
69 | forced |
6A | forget |
6B | fate |
6C | girl |
6D | gets |
6E | guess |
6F | girl's |
70 | house |
71 | hope |
72 | home |
73 | here |
74 | here. |
75 | home! |
76 | happen.... |
77 | houses |
78 | inventing |
79 | last |
7A | lost |
7B | language. |
7C | list |
7D | life. |
7E | laborer |
7F | letter |
80 | looked |
81 | lose |
82 | left-hand |
83 | mushrooms |
84 | make |
85 | mother |
86 | merchants |
87 | meet |
88 | most |
89 | only |
8A | ocean |
8B | opened |
8C | outskirts |
8D | president |
8E | please |
8F | probably |
90 | place |
91 | prison |
92 | pretty |
93 | palace |
94 | right |
95 | really |
96 | returned |
97 | running |
98 | ruins |
99 | right. |
9A | ruins, |
9B | road |
9C | rest |
9D | stone |
9E | seem |
9F | scattered |
A0 | seemed |
A1 | softly |
A2 | soldiers |
A3 | shape |
A4 | since |
A5 | surprised |
A6 | sure |
A7 | seen |
A8 | shouldn't |
A9 | somewhere |
AA | times |
AB | they |
AC | talk |
AD | tell |
AE | townspeople |
AF | taken |
B0 | they're |
B1 | travelling |
B2 | trying |
B3 | turned |
B4 | temporary |
B5 | than |
B6 | then |
B7 | too, |
B8 | thousands |
B9 | turn |
BA | treasure, |
BB | used |
BC | until |
BD | village. |
BE | vampire |
BF | while |
C0 | water |
C1 | went |
C2 | wondered |
C3 | written |
C4 | woman |
C5 | wind |
C6 | years |
C7 | you. |
General BG3
A general BG3 script is called by cop #$BD. In addition to the common features, it supports relocating the cursor, clearing specific sections of the screen, changing the tilemap character mask, printing non-BCD values in base 10, and various other conveniences. These scripts don't support the controls available in the dialogue engine, like page-advancing. The trade-off is that this engine is unrestricted and can draw any BG3 tile at any screen position.
All byte values from $00 to $11 are opcodes. They function as follows:
Opcode | Arguments | Notes |
---|---|---|
00
|
None
|
Ends the script, returning control to the caller. |
01
|
dw TargetTile
|
Sets the cursor position on the screen. The tile at screen position X,Y is represented by TargetTile = 2*X + $40*Y. |
02
|
dl IncludeAddr
|
Long-include. Includes the script at the long address IncludeAddr. |
03
|
db NewHighMask
|
Masks the high byte of subsequent tiles, i.e., the "vhopppcc" bits. The palette bits are cleared, then the byte is OR'd with NewHighMask. This is commonly used to change the text palette, by giving an argument of a palette ID left-shifted twice. |
04
|
dl AddressOfPointerList : dw AddressContainingIndex
|
Indexed include; given a list of pointers to text scripts, the value at AddressContainingIndex (in the bank of the pointer list) indexes the script to include. In other words, this includes the script at the address whose value is at AddressOfPointerList + twice the value at AddressContainingIndex. |
05
|
db NumDigits : dw AddressContainingNumber
|
Prints the hexadecimal numeric value of a memory address. The least significant NumDigits digits are drawn; leading zeroes are drawn as blank tiles. To print the value in base 10, store it as BCD or use code $0E instead. |
06
|
db SizeX, SizeY : dw StartTile
|
Draws a rectangle of blank tiles of width SizeX and height SizeY, with upper-left corner at StartTile = 2*StartX + $40*StartY. |
07
|
dw NumTiles
|
Clears NumTiles consecutive tiles, starting from the cursor position. |
08
|
db Character : dw AddressContainingCount
|
Writes consecutive copies of a given character. The number of instances to write is pulled from AddressContainingCount. |
09
|
db AltChar1,AltChar2,...,$FF
|
Probably not useful in non-Japanese localizations. Writes the given "characters", which are two tiles wide and high (i.e. 16px by 16px) and have a unique encoding: values between 0 and 7 index the tiles in BG3 VRAM from word-addresses $01F0/$01F1 to $01FE/$01FF; values between 8 and $17 similarly index VRAM tiles starting at $02E0. The latter set of tiles contains radar graphics. In the Japanese release, the former set was a buffer region, used for constructing room names; in the English localization that region is unused. The terminal byte can be any negative value. |
0A
|
None
|
Draws the player's HP bar. |
0B
|
None
|
Draws the last-damaged enemy's HP bar. |
0C
|
db Char1,Char2,...,$FF
|
Escape byte. Parses and prints subsequent bytes as characters even if they're in the code range. This is not generally useful. |
0D
|
None
|
Double-spaced newline: places the cursor two tiles below its initial position, as two carriage-returns + line-feeds, and sets the new initial position there. This is the normal newline character for inventory text. |
0E
|
dw AddressContainingNumber
|
Prints the decimal numeric value at a memory address, modulo 100 (i.e. only the tens and ones places). A leading zero is drawn as a blank tile. |
0F
|
db SizeX, SizeY
|
Draws a rectangle of blank tiles of width SizeX and height SizeY, from the cursor position. |
10
|
db DictEntryIndex
|
Includes text from the dictionary with entries indexed at $81eb0f. |
11
|
None
|
Single-spaced newline: places the cursor one tile below its initial position, as a carriage-return + line-feed, and sets the new initial position there. If writing text, the result is quite compact. |
Byte values $12 and above draw a character. Since the two 'c' bits of the character high byte are editable (via opcode $03), in principle all BG3 VRAM characters are accessible. However, only the first page is likely to be useful. The second page contains double-height dialogue characters, the fourth page contains a tilemap instead of VRAM characters, and the third page contains radar screen graphics, which are (inexplicably) drawn by manual RAM writes, not by a BG3 script.
Even so, the first BG3 VRAM page also includes many characters that are not generally useful, such as HUD graphics and dakuten/handakuten. The following bytes correspond to a printable character that is probably useful:
Byte | Character |
---|---|
1D | / |
20 | . |
21 | ! |
28 | [ |
29 | ] |
2A | * |
2C | , |
2D | + |
2E | - |
30 | 0 |
31 | 1 |
32 | 2 |
33 | 3 |
34 | 4 |
35 | 5 |
36 | 6 |
37 | 7 |
38 | 8 |
39 | 9 |
3A | : |
3C | ( |
3D | · |
3E | ) |
3F | ? |
40 | Space (black tile) |
41 | A |
42 | B |
43 | C |
44 | D |
45 | E |
46 | F |
47 | G |
48 | H |
49 | I |
4A | J |
4B | K |
4C | L |
4D | M |
4E | N |
4F | O |
50 | P |
51 | Q |
52 | R |
53 | S |
54 | T |
55 | U |
56 | V |
57 | W |
58 | X |
59 | Y |
5A | Z |
5B | – |
5C | ' |
5D | < |
5E | > |
5F | # |
60 | Transparent tile |
61 | a |
62 | b |
63 | c |
64 | d |
65 | e |
66 | f |
67 | g |
68 | h |
69 | i |
6A | j |
6B | k |
6C | l |
6D | m |
6E | n |
6F | o |
70 | p |
71 | q |
72 | r |
73 | s |
74 | t |
75 | u |
76 | v |
77 | w |
78 | x |
79 | y |
7A | z |
7C | _ |
7D | { |
7E | } |
7F | | |
80 | = |
81 | ; |
82 | " |
The dictionary entries indexed by opcode $10 all end with a space (character $40), so there is no need to add a space after the word when including it. The entries are:
Byte | Text |
---|---|
00 | Crystal |
01 | Diamond |
02 | Dark |
03 | Hieroglyph |
04 | Inca |
05 | Mystery |
06 | Prison |
07 | Plate |
08 | Psycho |
09 | Restores |
0a | Rearrange |
0b | Select |
0c | Statue |
0d | Uses |
0e | from |
Internal Data for Illusion of Gaia
| |
---|---|