Skip to content

Latest commit

 

History

History
628 lines (550 loc) · 26.7 KB

ANALYSIS.org

File metadata and controls

628 lines (550 loc) · 26.7 KB

Runtime Notes

  • SDL will FAIL unless you unset LANG.
  • You can run this in Xephyr!! pfexec Xephyr :1.0 & xauth extract - $DISPLAY | DISPLAY=:1.0 xauth merge - DISPLAY=:1.0 ./ioquake3.i386
  • This works RELIABLY
    • You may have to run ioquake a few times to make it go all the way through.
    • See build/run.sh !

Source Code Notes

Key Routines

NameLocationPurpose
mainsys/sys_main.cmain() of program, both client and server.
SV_Frameserver/sv_main.cPrimary simulation loop
SV_BotFrameserver/sv_bot.cInvoke Bot AI
BotAIStartFramegame/g_main.cInvokes (vm) call to SV_ClientThink, after maintaining
internal bot-related data structures. Seems relevant
to how bot input’s fed into clients.
SV_ClientThinkserver/sv_client.cSeems to take input commands and simulate a client.
ClientThinkgame/g_client.cActually runs the ‘pmove’ set of commands (after a validation
check, passing ctrl to ClientThink_real)

VM_Call Mappings

EnumFileActual Routine Called
BOTAI_START_FRAMEgame/g_main.cBotAIStartFrame
BOTLIB_USER_COMMANDserver/sv_game.cSV_ClientThink
GAME_CLIENT_THINKgame/g_main.cClientThink
GAME_CLIENT_COMMANDgame/g_main.cClientCommand
GAME_RUN_FRAMEgame/g_main.cG_RunFrame

Key Data Structures

NameLocationPurpose
playerState_tq_shared.hState of each player, as received
over network
entityState_tq_shared.hState of any entity, as received
over network. playerState_t is a
proper superset.

Renderer

  • Looks like the renderer is pluggable. RE_RenderScene and trap_R_RenderScene().
  • ‘tr’ in tr_main.c seems to maintain the draw graph. NO, it’s the vtable.
  • “RE” is the “refresh” module. tr_public.h:30

Snapshotting

SV_EmitPacketEntities

Scans a pair of entity lists (well, really offsets into the same list) , and encodes a delta-set into a ‘msg_t’. Handles entity +/- deltas between lists.

SV_WriteSnapshotToClient

Encodes header-data and the player state, and then calls SV_EmitPacketEntities

SV_AddEntitiesVisibleFromPoint

Builds a list of entities visible from a given position

SV_BuildClientSnapshot

sv_snapshot.c:446 Driver. Builds a snapshot by calling SV_AddEntitiesVisibleFromPoint, sorting them by entity number. Finally, updates svs.snapshotEntities[next % num] to be the same entityState_t (value) as svs.gentities[i].

SV_SendClientMessage

Iterates over all clients, and either:

  • sends the next fragment, if there is one for that client
  • calls SV_BuildClientSnapshot

Clearly the latter can create fragments.

Networking

see cg_snapshot.c

  • Note: L245 in cg_snapshot.c: CG_BuildSolidList(). Does this thing store them only as renderables client-side?. There has to be some simulation going on. Nah, just maintains a count of solid vs “trigger” entities.

Client-Side Networking

  • cl_main.c:CL_PacketEvent
  • cl_parse.c:CL_ParseServerMessage
  • cl_parse.c:CL_ParseSnapshot

Simulation

AI link-in to sv-main.

  • Maybe the easiest way is to just have my own routine feed in?
  • Some sort of configuration management is needed here.
    • Console variables! – NO. They have various archive/replication policies which could really mess things up.
    • Added new api under ls_variables.h
      VariablePurposeTo-enable
      lsp_simulateRun load simulatorlsp_simulate=1
      lsp_headlessDisable graphics displaylsp_headless=1
    • [X] Setup a new file, header, and set of flag facilities.
  • Can I just get SV_ClientThink called on the actual player’s commands?
    • There are flag-checks in ClientThink (invoked via trap to GAME_CLIENT_THINK). Make sure to take those out/reroute/etc.
  • SV_Main (conditional for simulation) invokes a botlib command that generates commands for client nr 0 (the actual one). Looks like right now, it’s 1+.
    • Umm, SV_Main() is server side. Client 0 isn’t special, it’s just the human in the game, and there’s always at least one (to start the game!). Server-side botlib commands can be studied, but should probably stay in as normal.

clientActive_t (client.h) has mouseDx/mouseDy members,

which may be sent directly over to the other side. This may be a decent place for a port of the simulator.

  • Actually it’s all taken care of in CL_ClientCmd, et al.

Current Bot path (incl networking)

  • They are created, from the looks of it, to fill a room that has too few other players. Looks like from G_BotConnect
  • G_BotConnect (game/g_bot.c:538) is called from ClientConnect (g_client.c:~915), the primary routine invoked upon client login.
  • Looks like ClientConnect could be called with a flag saying “I’m a bot”. Invoked from vm(GAME_CLIENT_CONNECT). 3 Callers to that VM:
    • sv_init.c:538 Change server to new map - SV_SpawnServer - takes all clients with it.
    • sv_ccmds.c:319 Map restart. SV_MapRestart_f
    • sv_client.c:526 SV_DirectConnect - looks like the initial connection (server side). Question is, how’s this work client-side? Can I just rewire that bit and make it look like a real player to the normal world?

Current Login Path

  • Must find primary login path for a normal client.
  • Then figure out where to get botlib running instead.
  • (cl_main.c) CL_Connect_f() sets up a connection state, which is then picked up by CL_CheckForResend
  • Primary packet input processing (client-side, at least) is CL_ParseServerMessage.
  • Server-side packet input processing is SV_ExecuteClientMessage
  • SV_PacketEvent->SV_ExecuteClientMessage->SV_ClientCommand ->SV_ExecuteClientCommand
  • Before the “netchan” can be set up, a “connect/challenge/connectResponse” cycle is executed.
    • CL_ConnectionLessPacket
    • SV_DirectConnect

With current login path

  • How do I bind the client to botlib?
  • Likely, the bots are designed for use only on the server.
  • [ ] Instead, check out the line “CL_SendCmd()” in

cl_main.c/CL_Frame().

  • If that was a botlib call instead, we may have a reasonable simulator on our hands.
  • [ ] G_BotConnect (int clientnum, qboolean restart)
    • clientnum (set to 0 should work)
    • restart = false
      • it’s for looking up existing bot data that was saved.
  • [ ] We can modify CL_SendCmd() to send in botlib commands instead (or added to) the local user.
  • [ ] How do bots sense their environment?

Botlib Analysis

  • bot_entitystate_t is the internal state of a bot (origin, angles, type, flags, model, weapon). Doesn’t seem to contain any health points, etc.
VTablePurpose
botlib_import_texported functions to botlib
aas_export_tprovided by aas_*.[ch]
ea_export_texported by be_ea.c
ai_export_texported by be_ai_*.[ch]
  • Acronyms (!!)
    • AAS - Area Awareness System
    • EA - Elementary Action
  • I maybe able to run an independent copy of botlib on the client side, with a few mods.. First, call GetBotLibAPI(), with my own set of ‘import’ functions (a vtable that’s passed in).
    • YES, start with copying SV_BotInitBotLib(), and modifying it as needed to setup a client-side botlib instance.
    • NOTE: all the imports passed in by SV_BotInitBotLib() are server-side. We’ll have to construct client-side equivalents where they can’t be directly ported over.

Client Data Analysis

  • playerState_t holds damage
  • Where are the client-side entities held? Can I find a routine to scan an area for me?
    • Scan the renderer.
    • The pointers in ls_core now hold references to all that now.

Integration

Plan

Overall, wire up a dumb “move-random” dummy client, then integrate botlib.

  • [X] Modify CL_SendCmd. It’s easier.
  • [X] A command-line switch, lsp_simulate (already wired into argc/argv, but I need to check it) wired into LS_Enabled. It should just flip s_enabled. Read in at first-run of LS_Enabled now.

BotImport

BotImport_Trace

One of the key integration routines. It links, most relevantly, to SV_Trace(), which goes to SV_Trace_r(), which then goes to scan sv_worldSectors[], a bsp tree of the world.

  • How do I go about scanning the client-side view of the world?
  • Find it in the renderer
    • No, that’s too poly-based. Let’s try client snapshot reading

instead. There’s playerState_t, a superset of entityState_t.

  • Well, let’s consider this in terms of the API SV_Trace()

actually needs.

  • What does SV_Trace() use? – it’s maintaining an internal BSP of all the entities in the world. I won’t be doing that, but then again, it’s to save server-side CPU. I can waste it on the client-side happily with little ill effect.
  • What can I use instead of the server-side BSP?
    • All I have is what the client receives. I can scan that, I suppose.
  • Looking at the interface for BotImport_Trace(), the result is stored in a parameter: bsp_trace_t *bsptrace, which is a structure in botlib.h:

    = typedef struct bsp_trace_s = { = qboolean allsolid; // if true, plane is not valid = qboolean startsolid; // if true, the initial point was in a solid area = float fraction; // time completed, 1.0 = didn’t hit anything = vec3_t endpos; // final position = cplane_t plane; // surface normal at impact = float exp_dist; // expanded plane distance = int sidenum; // number of the brush side hit = bsp_surface_t surface; // the hit point surface = int contents; // contents on other side of surface hit = int ent; // number of entity hit = } bsp_trace_t;

    • Note the last element `ent`, which corresponds to an entityState_t.
      • I could sort all entities across the vector, and scan that ways: nlogn * compare_time<entity>().
        • What’s the current representational shape of an entity? And that of the box being slid across the vector?
          TypeSliding BoxEntity
          AABBYUP
          OOBB

    CM_Trace usage: Called as:

    ArgGot?Calculate
    &trace
    start_l
    end_l
    symetricSize[0]
    symetricSize[1]
    model
    origin
    brushmask
    capsule

    Called from CM_TransformedBoxTrace(

    ArgGot?Calculate
    &trace
    (float *) clip->start
    (float *) clip->end
    (float *) clip->mins
    (float *) clip->maxs
    clipHandle
    clip->contentmask
    origin
    angles
    clip->capsule

    ) Called from SV_ClipMoveToEntities(moveclip_t)

    Called from SV_Trace

    ArgOrigin
    contentmaskarg
    startarg
    endarg
    minsarg
    maxsarg
    passEntityNumarg
    capsuleqfalse

BotImport_EntityTrace

“trace a bbox against a specific entity”

  • Just a wrapper around SV_ClipToEntity

SV_ClipToEntity

Calls SV_GentityNum

  • Just returns an address to a sharedEntity_t. A one-liner address-of from an array.

Calls SV_ClipHandleForEntity

  • two if()s and a final branch. Each calling one of

[CM_InlineModel, CM_TempBoxModel] Calls CM_TransformedBoxTrace

BotImport_PointContents

Calls SV_PointContents(point, -1)

BotImport_inPVS

Calls SV_inPVS(p1,p2)

Movements

Surprisingly easy. Just shove a usercmd_t into the client state, and it’ll get shoved across when it’s time.

Headless Operation

A priority, as Xepher’s unhappy with DISPLAY=:2.0 Initial questions

Where’s the X window setup?

  • At least some part is in R_Init() in renderer/tr_init.c
  • Additionally, it looks like it’s hooked in via RE_BeginRegistration(), which is an implementation of a vtable setup by GetRefAPI(). GetRefAPI()’s getting called from:

Where’s the rendering?

  • Uses for glVertex:
    FileUse
    tr_surface.cRB_SurfaceBeam, RB_SurfaceAxis
    tr_init.cglxInfo_f
    tr_backend.cRE_StretchRaw, RB_ShowImages
    tr_shadows.cR_RenderShadowEdges, RB_ShadowFinish
    tr_sky.c
    tr_shade.c
    tr_main.c
  • Skip it. It’s all in the callbacks coming from tr_init.
  • All calls to it are via the GetRefAPI() wrapper. It’s called from cl_main.c:2879. I can easily make that point somewhere else. Then it’s a matter of stubbing out the APIs.

    Note that I could just copy the entire subsystem and stub-out 99% of the code.

    APIE. Diff.StatusNotes
    Shutdown-
    BeginRegistration
    RegisterModel?Returns a qhandle_t
    RegisterSkin?
    RegisterShader?
    RegisterShaderNoMip?
    LoadWorld-
    SetWorldVisData-
    EndRegistration
    ClearScene
    AddRefEntityToScene
    AddPolyToScene
    LightForPoint
    AddLightToScene
    AddAdditiveLightToScene
    RenderScene
    SetColor
    DrawStretchPic
    DrawStretchRaw
    UploadCinematic
    BeginFrame
    EndFrame
    MarkFragments
    LerpTag
    ModelBounds
    RegisterFont
    RemapShader
    GetEntityToken
    inPVS
    TakeVideoFrame

Getting it done

  • Bug 1: r_fullscreen isn’t setup. I need to setup some console variables, it seems.
  • Holy shit, the fucker’s working. I forgot sound.
  • Sound’s up.

Command-line Execution

TOPLEVEL: These are console commands, how to run from the command line?

  • There’s already likely a way
  • I just need to expose them simply.
  • + signs are separators, just like \n

Comment from common.c:353:


COMMAND LINE FUNCTIONS

  • characters seperate the commandLine string into multiple console command lines.

    All of these are valid:

    quake3 +set test blah +map test quake3 set test blah+map test quake3 set test blah + map test


NOTE: Port Numbers

Sometimes the client won’t connect unless it has the right port # for the right instance of quake. This shouldn’t be a problem for big-simulation, as there will only be one instance on the server vhost.

Still, if there’s a problem, the server’s log will show which port it could connect to. Then run the client with a host_ip:host_port command-line

Run the client from the command line, with no user interaction

  • What’s the connect() string like? cl_main.c:1510: Com_Printf( “usage: connect [-4|-6] server\n”);
  • [X] Wrap that fucker in a shell script
    • Yup, called build/client.sh Need to add in the option for running botlib, however.
    • [X] Stop this nslookup for update.quake3arena.com first
  • SUCCESS! The client runs, automatically logs in, and respawns. Holy Fuck.
  • use +set name “Foo” to set the client’s name.
  • Full example: DISPLAY=:1.0 ./ioquake3.i386 lsp_headless=1 lsp_simulator=1 +connect 24.103.248.74:27961 +set name “Simulator1” & (note that DISPLAY=:1.0 isn’t actually used at all, but it’s safer in case I screw something else up)

Run the server from the command line, with no user interaction

First guess at the right routine: SV_Startup sv_init.c:268

  • Called only by: SV_SpawnServer sv_init.c:404
  • Called in: sv_ccmds.c:204,268.
CommandFunctionDesc
heartbeatSV_Heartbeat_f
kickSV_Kick_f
banUserSV_Ban_f
banClientSV_BanNum_f
clientkickSV_Kick_f
statusSV_Status_f
serverinfo
systeminfo
dumpuser
map_restart
sectorlist
map
killserver
say
rehashbans
listbans
banaddr
exceptaddr
bandel
exceptdel
flushbans
  • Odd, doesn’t the server have a dedicated server mode? I’m a moron. ./ioq3ded.i386 +map q3dm1

AI Client [33%]

Wire-up lsp_simulator=1 to my stub Create-Command function

Initialize BotLib [50%]

Fill in function pointers

Initialize data structures for bots

  • Check out G_AddBot g_bot.c:564, copy its contents, alter as needed.
  • I (think I) really just need to add a properly-initialized value to g_entities (g_main.c:38).

Use botlib for client-side simulation [0%]

See SV_BotFrame(), called by SV_Frame() in sv_main.c SV_BotFrame() just calls VM_CALL(.., BOTAI_START_FRAME, ..); -> ai_main.c:1379 - BotAIStartFrame(int time) BotAIStartFrame() is the PRIMARY routine for bot computation.

Rough outline of what to do

  1. in CL_Init(), call VM_Call( QUESTION, CLIENT_SETAI, QUESTION)
  2. Make the VM call in CL_Frame if I’m simulating
  3. Translate the resulting botlib commands back to usercmd_ts and transmit them as our own.

VM Questions

There are three:

VM NameVarDesc
uivmvm_ui
cgvmvm_cgame
gvmvm_game

Which one holds botlib?

The system one. Can I access it from the client?

  • Look at initialization
  • Currently, no. I may be able to later. WARNING I may have to implement/initialize some traps for botlib to call back into the C runtime (as it’s not otherwise used on the client side).
  • See SV_GameSystemCalls sv_game.c:303 Write a new routine for system calls, copy-paste the botlib handlers (and standard-looking ones). assert(0) on the rest.

OPEN QUESTION routines [66%]

BOTLIB_GET_SNAPSHOT_ENTITY

SV_BotGetSnapshotEntity WTF is it doing? Two args (client, sequence) Final eqn: svs.snapshotEntities[ (frame->first_entity+sequence) % svs.numSnapshotEntities ].number snapshotEntities is an array of entityState_t. To figure this out, I’ll have to understand how snapshotting works, exactly.

AH. For the given client ‘client’, it returns the nth entity sent to it, in the order the entity snapshots were sent.

Note that it returns ‘sequence+1’ BotAI() -> BotDeathmatchAI() -> BotCheckSnapshot() -> BotAI_GetSnapshotEntity() ->

  • [X] Can I get away with just one valid value for ‘client’ ?
  • So far, I’m only seeing this method called from a child of ‘BotDeathmatchAI()’, which we probably won’t use. WRONG, it’s called from BotAI, which sounds pretty important. Called for every bot. We only have one, so can we get away with this?
  • YES. It’s only caring about its own setting.
    • [ ] Do I need more information than what the client already has?
  • Well, ‘g_entities’ has a entityShared_t, which we don’t get on the client. And that’s used quite a bit in botlib.
  • Does it NEED it?
BOTLIB_GET_CONSOLE_MESSAGE:

SV_BotGetConsoleMessage

BOTLIB_USER_COMMAND

SV_CLientThink

Note that I’ll have to build a .pk3 for the new routines

  • Some are vm-side. Thankfully I wrote down the procedure somewhere for this. On paper, I believe. I’ll have to add a new ENUM for it.
    • Existing path:

VM ->ConsoleCommand() -> Svcmd_AddBot_f() -> G_AddBot().

  • Our path: the same one used for command-line arguments That’s setup in CL_Init(), which we’re already modifying pretty heavily for setting up our normal state.
  • Consider making a new vm’d .c file for this stuff. Look at the visibility of data structures, etc. for this.

Call botlib during client-side simulation

  • [ ] Call the VM function, just like SV_BotFrame() did.
  • See CL_Frame() for where to do it.
    • [X] Figure out where the return value from the botlib init

routine is used. My guess is it’s fed into the/a vm somehow?

Wire up command-creation from botlib

  • [ ] Convert the resulting botlib commands back into usercmd_ts.

NO, do it without botlib

botlib woudl probably require compiling the whole game into the binary. Which is likely painful (and kills your ability to use the manifest on ec2). Instead, put together a console var (or find one(!)) that has the curent player’s location (or all of them, in an array), so I can print them. Then print key location sin a level, and make them a hard-codd table.

Alternate AI Client

Get your position

ALREADY THERE: `viewpos` in the console

Get a hard-coded map of the level

Start moving ala A* and the distribution from clients.

Instrumentation

I got a little help from Adam Leventhal (creator of dtrace(1)) on this one.

HOWTO: native-side, regular dtrace probes. vm-side, traps from g_public’s gameImport_t.

See status.org for the real status of these:

  • Number of objects under simulation
    • Total count
    • Number of logged-in players
  • Core loop time
  • Per-player simulation time
  • Per-(nonplayer) object simulation time
    • Projectiles
  • Per-player synchronization time
  • Total memory used

(this is based on observing the sizes reported from top(1)).

  • Total bandwidth
    • In
    • Out
    • Counters for each

I’m modifying the VM environment with new traps (at the end, so it won’t break backwards compat), but I’ll have to build new .pk3 files to handle my changes.

I can do a git status-compare against the original checkin to see what I’ve changed.

Random Scalability Notes

  • g_public.h:64 - Max clients must be <= 32 for ‘singleClient’
  • I’ve seen other uses of a maxclient=32, such as using a normal uint32_t for a bitmask for clients (snapshotting, I believe).