# SAPIENS — notes for agentsPort to PHP (JumPHP) of **SAPIENS**, the prehistoric adventure game of Didier & Olivier Guillion(Myriad, 1986 / 1996 / 2017), from the C sources published under **GPL v3** in 2018(`sapiens_03_04_2018.zip`). The data files of the original (sprites `.BRT`, sounds, pictures, texts)are GPL too: they are in the repository.Read the framework rules first: `../../AGENTS.md`.## Layout| Path | What ||---|---|| `src/Scene/GameScene.php` | the only scene: runs `Game::main()` (a generator) and shows `mainWindow` || `src/Sapiens/Game.php` | the globals of `SAPGLOB.C` / `SAPIENS.H`, mob numbers (`MOB_*()`), `InitMobs` || `src/Sapiens/*Trait.php` | one trait per C file, same function names as the C code || `src/Sapiens/Port.php` | an 8-bit offscreen (`CGrafPtr`): rows of bytes, indexes in the Mac palette || `src/Sapiens/Mac.php` | the Mac system palette (`Color2Index`), `TabCvtCoul` (`Mac::st()`), shadows || `src/Sapiens/Anim.php` | the animation bytecode of the side view (`anjm1`... `debban`) as one flat table || `src/Sapiens/Text/{Fr,Gb,Es}.php` | the texts (generated by `tools/convert-texts.py`, byte for byte) || `assets/data/mobs/*.BRT` | the sprites of the original (read at run time) || `assets/data/*.bmp`, `strings.json`, `assets/sfx/bruit*.wav` | made by `tools/convert-rsrc.php` and `tools/convert-images.php` from the resource fork || Trait | C file | Content ||---|---|---|| `PlatformTrait` | SAPWCLAV.C, SAPWIND.C, SAPWPREF.C | timers, keyboard / mouse, `Random()`, `init_graph`, preferences || `GfxTrait` | SAPWBRT.C, SAPWCVT.C | `.BRT` loading, `Affmobbrt`, `Affmob3d`, `affomb`, `CopyOffscreen`, QuickDraw subset || `TextTrait` | SAPTXT.C, SAPWCAR.C, SAPWMESS.C | texts by language, characters, message line || `ListTrait` | SAPLIST.C | `main`, menus, lists, take / drop / eat / heal || `RelTrait` | SAPREL.C | relations, dialogues, clock (`chrono`), encounters, sleep, observe || `CombTrait` | SAPCOMB.C | side view, fights, animation interpreter || `GestTrait` | SAPGEST.C | flint knapping, character creation, load / save, intro || `World3dTrait` | SAP3D.C | world objects, world generation, lightning, `Final` || `Render3dTrait` | SAPW3D.C | the landscape renderer (sky, stars, ground, billboards) || `TreeTrait` | SAPWARB.C | the trees of the side view || `MapTrait` | SAPWCART.C | the map || `MusicTrait` | SAPWMUS.C | sound effects (`bruit`) and music |## Porting conventions (keep them when changing the port)- **Faithful**: same function names, same globals (properties of `Game`), same logic. Fix a bug by reading the C code (unzip `sapiens_03_04_2018.zip` from myriad-online.com into `.original/` at the root of the jumPhp clone: the conversion tools read it there), not by redesigning.- **Generators**: a C function that waits (busy loops on `TickCount`, `sapiensTimer`, the mouse button, `litclav`...) is a PHP generator (`: \Generator`); each loop iteration that waits does `yield;` (= end of a frame: the scene shows `mainWindow` and resumes the game the next frame, the timers having advanced). Callers use `yield from $this->f(...)`; a generator that returns a value: `$v = yield from $this->f()`. Do not yield in a function that the C code runs without waiting.- **Integers**: `unsigned char` globals / fields: `& 0xFF` after arithmetic when the C code relies on the wrap (e.g. `faim -= 2; if (faim > 253)`), `unsigned short`: `& 0xFFFF`; `(char)x` = `self::sc($x)`, `(short)x` = `self::ss($x)`, C `long` overflow = `self::l32($x)` (the browser has 32-bit ints!). Integer division: `intdiv()` (truncates toward 0 like C). `>>` on negative values is arithmetic like C. `char` arrays of SAPGLOB keep the C initialiser values: use `sc()` where C reads them as signed.- **Pointers**: `struct sap *` = `Sap` object, `struct liste *` = `Liste` object; animation pointers (`char *` into `anjm1`...) = int offsets in `Anim::ANIM` (0 = NULL); palette index tables (`short * tabIndex`) = 256-byte strings (byte map: `ord($map[$i])`); `Rect` = `[left, top, right, bottom]` (C `Rect` initialisers are `{top, left, bottom, right}`: swap!). `Point` = `[h, v]`.- **Drawing**: the ports are `$this->Ecran1` / `Ecran2` (512x200, the view), `$this->debecr` (640x400, the work screen), `$this->mainWindow` (640x400, what is shown), `$this->BuffMouse`; the current port is `$this->curPort` (`AcamSetPortOffscreen($port)`). Colours are system indexes (`Mac::st($stColour)` for an Atari ST `0x0RGB` colour). Text: `affcar()`, `DrawStringBrt()` (the bitmap fonts of the game: the original "non smoothed" mode). There is no SapZoom (the engine scales the 640x400 view).- **Removed**: Mac menus and dialogs (replaced by in-game screens), file pickers (saves are in `var/` through `Options`/`Storage`), registration (`IsEnreg()` is always true), `visu2`, `OLD_VERSION` blocks, Windows files.## Differences with the original (on purpose)- Text uses the bitmap fonts of the game (GENEVA10/14 mobs) instead of the smoothed Helvetica.- New adventure: world counts capped (300 clouds, 100 birds, 2000 rocks, 3000 trees) for speed; trees / rocks are culled with a grid. Stars are a pre-drawn band (other random sequence at night).- Dialogs of the Mac version (confirmations, file pickers, preferences, informations) are in-game choices (`GestTrait::gestChoix`): 3 save slots in the options (`sapiens_save_<n>`, original bytes in base64).- Music: rendered offline (`tools/render-music.php` + ffmpeg) to `assets/music/sapiens1.ogg` / `sapiens2.ogg` with the sampled instruments of `Sapiens1.mux` / `Sapiens2.mux` (Harmony Assistant files, decoded by `tools/Mux.php`: LZW, staves, log-coded samples). Music 1 is their score note for note; music 2 is made by the accompaniment engine of Harmony (patterns over chords, not reproduced), so its notes come from the 1987 score it was made from (`MUSE.C`, same General MIDI programs).- English texts: one word replaced by "SPEAR" (lint of the framework).- Touch screens (`touchMode`): no steering with the pointer, the on-screen pad sends the keys; a held click lasts until the game reads it (GameScene); no keyboard: the name of a new character is the last one (a tap accepts it) and man / woman is a `gestChoix`.## Run / test```bashbin/jumphp test sapiens # lint, phpstan, smoke (demo mode)bin/jumphp shot sapiens 400 # var/screenshots/shot.pngJUMPHP_SCENE='App\Scene\GameScene' JUMPHP_SCENE_PARAMS='{"demo":true,"seed":3}' bin/jumphp shot sapiens 15000 # random botbin/jumphp build sapiens && bin/jumphp webshot sapiens 8```