28 KiB
Sistema d'entitats
Registre del disseny, els treballs fets i els pendents per al sistema d'entitats (sarcòfags, porta, momia, antorxa, trampes, roca) d'aee_2026. Material de referència per a decisions futures — no és documentació d'API.
Estats possibles: DONE, WIP, DEFERRED, WONTFIX.
Convenció de col·lisió i anchors (fase 3)
sprite.size(YAML entitat): mida en píxels del frame de render.tile_size(YAML entitat): footprint lògic en tiles. Només el consumeix Sarcophagus per al perímetre; a la resta és declaratiu.collision: { size: [w, h], offset: [x, y] }(opcional al YAML entitat): sub-rect de col·lisió dins del sprite. Sioffsetfalta, es calcula automàticament bottom-center (falsa perspectiva típica). Sense bloc,Entity::getCollisionRect()=pixel_rect_sencer.anchor(opcional al YAML entitat):top_left(per defecte) ofeet.feetvol dir que elposition: [col, row]del map és el tile dels peus; el sprite es centra horitzontalment sobre el tile i s'alinea la vora inferior del sprite amb la vora inferior del tile. Els sarcòfags tenen bottom-align hardcoded i no usenanchor.
1. Context i objectiu inicial
aee_2026 és un clon tipus Oh Mummy! amb estètica Amidar. Els sarcòfags eren fins ara rectangles purament geomètrics calculats a Map::getSarcRect; no hi havia entitats de joc (momies, antorxes, trampes, portes).
Objectiu inicial (prompt de partida):
- Classe base
Entityamb posició tile+pixel, sprite/animació opcional,update(dt),render(), flags,onPlayerContact. - Jerarquia:
Decoration·Interactable·Hazard(→StaticHazard,DynamicHazard). - 7 classes concretes:
Torch,Sarcophagus,Door,Mummy,RollingRock,WallTrap,FloorTrap. Animationcom a component reutilitzable (composició, no herència).EntityFactory/EntityLoaderamb registre per nom — afegir una entitat no ha de tocar el factory.- Sarcòfag amb perímetre en tiles i estat CLOSED/OPENING/OPEN; emet
sarcophagus_opened. - Porta amb estat CLOSED/OPENING/OPEN; s'obri quan tots els sarcs oberts; contacte →
level_complete. - Momia amb IA
corridor_wanderer(hook virtual). EntityManager+ event bus minimalista (pub/sub).- Placeholders gràfics (GIF paletitzats) + YAMLs de plantilla per cada entitat.
- Smoke tests stand-alone (sense frameworks externs).
2. Fase 1 — Andamiatge pur (DONE)
Codi, dades i tests aïllats del flux viu del joc: el sistema existeix i funciona, però GameScene encara no l'instancia.
Fitxers creats
| Path | Paper |
|---|---|
| source/game/entities/entity.hpp / .cpp | Classe base; update, render, onPlayerContact(Player*), notifyTileEntered |
| source/game/entities/decoration.hpp | Header-only; collides=false, interactable=false |
| source/game/entities/interactable.hpp | Header-only; afegeix interact(Player*) |
| source/game/entities/hazard.hpp | Hazard + StaticHazard (cicle actiu/inactiu) + DynamicHazard (velocitat + step) |
| source/game/entities/animation_component.hpp / .cpp | Frames/fps/loop; tick, currentFrame. Completada d'inici si 1-frame no-loop |
| source/game/entities/event_bus.hpp / .cpp | Pub/sub amb std::variant payload |
| source/game/entities/entity_factory.hpp / .cpp | Macro REGISTER_ENTITY + registre estàtic per nom |
| source/game/entities/entity_loader.hpp / .cpp | Cache de plantilles YAML + readSpawns |
| source/game/entities/entity_manager.hpp / .cpp | Contenidor + comptador de sarcs + fan-out de tile |
| source/game/entities/concrete/torch.* | Decoració animada |
| source/game/entities/concrete/sarcophagus.* | Perímetre tiles ortogonal + estats |
| source/game/entities/concrete/door.* | Estats + open() + emissió level_complete |
| source/game/entities/concrete/mummy.* | chooseDirection virtual (aleatori per defecte) |
| source/game/entities/concrete/rolling_rock.* | Línia recta, direcció inicial del YAML |
| source/game/entities/concrete/wall_trap.* | StaticHazard amb cicle |
| source/game/entities/concrete/floor_trap.* | Igual que wall_trap, semàntica distinta |
| tools/entities/generate_placeholders.py | Genera 7 GIFs paletitzats amb Pillow |
data/entities/<name>/<name>.yaml + .gif |
7 plantilles d'entitat + 7 placeholders |
| data/maps/test_entities.yaml | Nivell mínim per al smoke |
| tests/test_entities_smoke.cpp | Executable stand-alone (T1–T8, ampliat a T9 a fase 2) |
Mides i colors dels placeholders
| Entitat | Mida px | Color |
|---|---|---|
| torch | 16×16 | taronja |
| door | 16×16 | marró fosc |
| mummy | 12×24 | beige |
| sarcophagus | 48×16 (3×1 tiles) | daurat |
| rolling_rock | 16×16 | gris |
| wall_trap | 16×16 | vermell fosc |
| floor_trap | 16×16 | vermell clar |
Decisions clau
onPlayerContact(Player*)amb punter nullable en comptes de referència: permet smoke tests sense un Player real, i obre la porta a triggers d'àrea sense jugador específic.Sarcophagusha rebut dues variants de ctor: amb i sense ancoratge. La sense ancoratge la crea el factory des d'una plantilla YAML;place(col, row, predicate)s'invoca després quanEntityManagerconeix la posició.- Registre estàtic via macro
REGISTER_ENTITY— perquè funcioni, tots elsconcrete/*.cppentren directament aAPP_SOURCES(no a una STATIC lib sense--whole-archive). - Animació d'1 frame no-loop marcada com a
completed()des de la construcció: permet que sarcs i portes amb placeholder transitin OPENING → OPEN al següentupdate.
Smoke tests fase 1
Target CMake test_entities_smoke al final de CMakeLists.txt. Cobria T1–T8:
- T1 factory: crea tots els 7 tipus per nom.
- T2 loader: llegeix spawns de
data/maps/test_entities.yaml. - T3 event bus: subscribe/publish/unsubscribe amb payloads variats.
- T4 perímetre de sarc: 3×1 tiles, 8 perimetrals, notificació → OPENING.
- T5 end-to-end: 2 sarcs + 1 door, obrir sarcs → door OPEN → contacte → level_complete.
- T6 animation: frames multi + loop + completed.
- T7 plantilles: totes les 7 es carreguen sense excepció.
- T8 decoration flags.
Tots passats al tancament de la fase 1. El binari del joc, sense canvis, continuava arrencant net sota xvfb-run.
3. Fase 2 — Integració al GameScene (DONE)
Cableja l'andamiatge al flux normal del joc. En arrencar amb pyramid.yaml, les entitats es carreguen, tenen sprite visible i poden reaccionar als moviments del Player.
Diagnòstic clau
La primera proposta va suggerir refactor del sarcòfag a "4 costats en píxels" perquè l'origen del canvas (40, 64) no era múltiple de 16. Diagnòstic erroni: el tilemap està en grid 16×16 relatiu al seu propi origen (origin: [-8, 0] a pyramid.yaml), no al canvas. La conversió correcta és:
col = floor((player_center_x - tilemap.origin_x) / tile_size)
row = floor((player_center_y - tilemap.origin_y) / tile_size)
Amb això, el sarc (0,0) amb canvas rect (40, 64, 48, 16) → tile anchor (3, 4), span (3, 1). El model de tiles es manté intacte.
Canvis aplicats
Mapglobals noves (map.hpp:36-40):tile_size,tilemap_origin_x,tilemap_origin_y. Poblats aloadFromdes deyaml["tile_size"]iyaml["origin"].EventBuspayload ampliat ambstruct TilePos { int col; int row; }+ overloadpublish(name, TilePos).Entityambstd::shared_ptr<Surface>,setSurface/getSurface/setPixelRect. Per evitar arrossegarSurfaceal smoke,Entity::render()és no-op i el blit real el faEntityManager::renderAll, centralitzat.PlayerambsetEventBus(EventBus*)+getCurrentTile()(fórmula correcta ambtilemap_origin_*+tile_size). Al final d'update, si el tile canvia, publicaon_tile_enteredamb payloadTilePos.EntityManager::loadFromLevel(viu a entity_manager_loadlevel.cpp, TU separat per aïllar la dependència aResource::Cache):sarcophagus→sarc_position→Map::getSarcRect→ tile anchor per al perímetre.- resta →
position(tile coords del tilemap) → píxels viacol*tile_size + origin.
EntityManager::renderAll(al mateix TU). Blit directe deSurface::renderapixel_rect_.EntityManagersubscriuon_tile_enteredal ctor: quan repTilePos, fa fan-out vianotifyPlayerEnteredTile(col, row)a totes les entitats.GameScene(game_scene.cpp):- Membre
EntityManager entity_manager_. - Ctor:
loadFromLevel(config_.map_yaml)després de carregar mapa; subscripcionslevel_completeiplayer_hit(log astd::cout);players_.back()->setEventBus(&entity_manager_.getBus()). update:entity_manager_.update(DT)després dels Players (així els sarcs reben els tiles del mateix frame).render:entity_manager_.renderAll()entretilemap_.render()i els Players.
- Membre
data/maps/pyramid.yamlamb 5 spawns reals: torch (2,4), sarcs (0,0) i (3,4), door (10,14), mummy (10,7).
Partició per aïllar dependències
El smoke test és stand-alone (no Resource::Cache, no Surface, no audio). Per no arrossegar tot el pipeline al smoke, EntityManager es divideix:
- entity_manager.cpp:
ctor/dtor/add/setDoor/addSarcophagus/update/notifyPlayerEnteredTile— linkat al smoke. - entity_manager_loadlevel.cpp:
loadFromLevel+renderAll— NOMÉS al binari principal.
Al CMakeLists: ENTITY_SOURCES conté els fitxers core; APP_SOURCES afegeix entity_manager_loadlevel.cpp explícitament fora de ENTITY_SOURCES.
T9 afegit al smoke
testLoadFromLevel — Map::loadFrom(pyramid), verifica tile_size=16, origin=(-8, 0); EntityLoader::readSpawns retorna 5 spawns amb els tipus esperats; la conversió sarc-grid (0,0) → tile anchor (3,4) és correcta; fan-out via bus: visitar els 8 tiles perimetrals d'un sarc a (3,4) mida 3×1 fa passar l'estat a OPENING.
Verificació de tancament
make build: OK, 0 warnings nous../build/test_entities_smoke: 9/9 OK.ctest --output-on-failure: 1/1 suite passed.timeout 5 xvfb-run ./build/aee_2026: 5s sense crash, stdout inclou:Surface : torch.gif ... LOADED Surface : sarcophagus.gif ... LOADED (×2) Surface : door.gif ... LOADED Surface : mummy.gif ... LOADED EntityManager: 5 entitats carregades des de data/maps/pyramid.yaml
4. Memòria durable registrada
Durant la fase 1 l'usuari va establir una regla permanent desada a memory/feedback_smoke_test_mandatory.md (fora del repo):
Sempre que s'afegeixi o modifiqui lògica no trivial, s'ha d'estendre un smoke existent o crear-ne un de nou que exerciti aquella lògica, i executar-lo abans de reportar la tasca com a acabada. Al missatge final cal indicar explícitament si els smoke tests passen.
Es respecta tant a la fase 1 com a la 2.
5. Pendent / DEFERRED
Gameplay
| ID | Descripció | Estat |
|---|---|---|
| ENT-IA-01 | Moviment viu de la momia: Mummy::step camina en línia recta validant Map::collides + Map::insidePlayfield a cada frame. En topar, recull perpendiculars lliures (evitant U-turn llevat de cul-de-sac) i crida chooseDirection. Cobert per T13. |
DONE |
| ENT-IA-02 | IA de la momia amb bias cap al jugador (weighted_towards_player). Hook chooseDirection deixat preparat; només cal una subclasse/override. |
DEFERRED |
| ENT-HAZ-01 | Trampes actives amb dany real al jugador. onPlayerContact publica player_hit via EntityManager::checkPlayerCollisions, però encara no hi ha sistema de vides. Caldrà cablejar vides al Player. |
DEFERRED |
| ENT-ROCK-01 | Col·lisió de la roca amb sarcs/murs. RollingRock::step valida Map::collides/insidePlayfield i s'atura quan bloqueja (setDirection 0,0). No rebota per disseny. |
DONE |
| ENT-DOOR-01 | Retorn a la pantalla de títol al level_complete. Ara només log a stdout. Pendent cablejar la transició de Scene. |
DEFERRED |
Render i geometria
| ID | Descripció | Estat |
|---|---|---|
| REND-01 | Z-order fi entre entitats i Player: GameScene::render construeix una llista {Entity*, Player*, feetY}, la ordena amb stable_sort ascendent i dibuixa per ordre. Els sprites amb peus més amunt queden darrere dels que tenen peus més avall. |
DONE |
| REND-02 | Animacions multi-frame. renderAll clipa el frame actual via src_rect calculat amb Entity::getAnimation()->currentFrame() sobre una graella left-to-right / top-to-bottom; columnes deduïdes de surface->getWidth() / pixel_rect.w. Así un strip 1×N, N×1 o N×M funciona igual. Torch primer consumidor real (4 frames @ 8 fps). |
DONE |
| REND-03 | Paleta compartida amb el tilemap. Cada GIF placeholder té la seua paleta pròpia (Pillow); funciona perquè Surface desa paleta per surface. Amb l'art real pot caldre unificar per evitar mismatch cromàtic. |
DEFERRED |
Perímetre i col·lisió
| ID | Descripció | Estat |
|---|---|---|
| PER-01 | blocked_predicate al Sarcophagus per descartar tiles ocupats per altres sarcs. Actualment loadFromLevel passa {}. Els spawns de demo eviten sarcs veïns; al primer disseny amb 4×5 sarcs adjacents caldrà connectar-ho (amb Map::collides traduït a tiles). |
DEFERRED |
| PER-02 | Perímetre diagonal opcional. Ara el perímetre és ortogonal (N/S/E/O). Si volem variants amb cantonades exigides, es pot afegir una flag al YAML de l'entitat. | WONTFIX (per ara — el comportament Oh Mummy! clàssic és ortogonal) |
Infraestructura
| ID | Descripció | Estat |
|---|---|---|
| INF-01 | Dead-code elimination del registre estàtic. Funciona mentre concrete/*.cpp viuen a APP_SOURCES. Si algun dia es modularitza a STATIC libs caldrà --whole-archive o un entity_registry_bootstrap.cpp amb cridades explícites. |
WONTFIX (vigilar) |
| INF-02 | Multiplayer: el bus del nivell és únic. Quan hi hagi múltiples Players, cal decidir si el on_tile_entered porta també el player_id. Ampliació trivial de TilePos o struct nou. |
DEFERRED |
| INF-03 | Integrar animacions multi-frame al YAML: els camps frames: [0] actuals són placeholders. Quan arribi l'art, el loader ja és tolerant — només cal omplir els vectors. |
WONTFIX (ja previst al codi) |
| INF-04 | Hook on_tile_entered emet cada frame que el tile canvia, no cada frame que el jugador es mou. Si algun dia es vol granularitat sub-tile (p.ex. "passar pel centre exacte"), caldrà un event separat. |
WONTFIX |
Tests
| ID | Descripció | Estat |
|---|---|---|
| TST-01 | Test d'integració amb loadFromLevel real: el smoke actual evita el Cache per no arrossegar dependències. Un test separat amb el Cache enllaçat podria verificar el blit, però demana instanciar Screen/Canvas. |
WONTFIX (l'smoke d'arrencada sota xvfb-run ja cobreix el camí viu) |
| TST-02 | Test de moviment de la momia: T13 exercita step lliure (avança) i step contra paret (gira sense avançar). | DONE |
6. Fase 4 — Moviment i interacció (DONE)
Tanca els hooks d'IA i col·lisió que quedaven apuntalats a les fases 1–3.
Mummy::stepimplementat: desplaçament recte validant playfield i sarcs; gir a cul-de-sac o intersecció viachooseDirection.RollingRock::stepigual, però s'atura en lloc de girar.Entity::getCollisionRect()+getFeetY(): caixa AABB encastada al sprite (falsa perspectiva, opcional via bloccollision:del YAML).- Anchor
feetal loader:position: [col, row]és el tile dels peus; el sprite es centra en X i bottom-aligned en Y. - Sarcòfag amb caixa lògica:
placeSarcophagusaplica unCollisionBoxque retalla la meitat superior del sprite (alçada) perquè no dispari falsos contactes. Map::insidePlayfield(box): valida la caixa candidata delsstep.EntityManager::checkPlayerCollisions(players): detecta solapament base-box del player ambgetCollisionRectde cada entitat ambcollides()=true, i cridaonPlayerContact. Cablejat alGameScene::update.- Z-order:
GameScene::renderordena {entitats, players} pergetFeetY()ascendent i dibuixa en aquell ordre (interleaved). Player::getBaseBox()exposat com a API pública.- Smoke tests: T12 (CollisionBox), T13 (Mummy::step), T11 (sarc YAML), T10 (torch anim). Total 14/14.
7. Fase 5 — Convenció de caixa "peus" com el jugador + animacions direccionals (DONE)
Alinea la momia amb la convenció de col·lisió i animació del Player, i soluciona regressions de navegació derivades del canvi de caixa.
Caixa concèntrica amb el rectangle-base
collision.base_size: [bw, bh](nou camp YAML, opcional). Si apareix, la caja no s'alinea a la vora inferior del sprite sinó que queda concèntrica amb el rectangle-basebw×bhdels peus (elsbhpíxels inferiors del sprite). Fórmula:off_y = sprite_h − (base_h + H) / 2. Sensebase_size, comportament antic (alineada a la vora inferior del sprite).anchor: feetara alinea la caja, no el sprite. Si l'entitat téCollisionBox, el loader col·locapixel_rect_perquè la caja quedi exactament al tile. Sense caja, fallback a la convenció antiga (sprite centrat horitzontalment + vora inferior al tile). Per la momia: sprite 12×24 a (tile_x+2, tile_y−10); caja 16×16 a (tile_x, tile_y) — omple el tile sencer com fa el jugador.mummy.yaml:collision: { size: [16, 16], base_size: [12, 12] }. Abans era[12, 12]alineada a baix; visualment quedava "enganxada al mur inferior" dins dels corredors horitzontals.
Snap-to-edge per girar al playfield
La caja 16×16 té exactament l'amplada del corredor (16 px). Amb el moviment contínu speed × delta_time (~0.66 px/frame), la momia s'aturava a una posició subpíxel dins de l'última cel·la (p. ex. x=279.87 en lloc de x=280) i qualsevol gir perpendicular col·lidia amb el sarc adjacent al limit. Mummy::snapToPlayfieldEdge redueix el dx/dy fins a la vora exacta quan el candidat sortiria del playfield; el gir posterior ja parteix d'una posició alineada amb el lane.
Animacions direccionals
mummy.gifa 12 frames (graella 12×1) amb el mateix esquema que el jugador: 0–3 walk_down, 4–7 walk_up, 8–11 walk lateral. Sense variantsstand_*(mai no para).Mummyté tresAnimationComponentprivats + un helperchangeDirection(dx, dy)(únic punt d'entrada per canviar direcció) que selecciona l'animació i aplica el flip:dx ≠ 0→ lateral amb flip horitzontal segons signe;dy < 0→ up;dy > 0→ down.Entity::flip_a la base +getFlip().renderEntityel passa aSurface::render, que ja suportavaSDL_FlipMode. Les entitats estàtiques queden aSDL_FLIP_NONEautomàticament.
Pipeline de build: sincronització de data/
Canvi a CMakeLists.txt: la còpia de data/ al build era un add_custom_command(POST_BUILD), que només es dispara quan el binari es relinkeja. Si s'editava només un YAML o un GIF sense tocar codi, build/data/ quedava obsolet. Ara és un add_custom_target(sync_data ALL ...) amb dependència del target principal — els custom targets sense BYPRODUCTS es consideren sempre out-of-date, així que la còpia s'executa a cada cmake --build.
Refactors de suport
Mummy::steppartit:snapToPlayfieldEdge,collectDirectionPools,applyChoice— per mantenir la complexitat cognitiva sota el llindar de clang-tidy (< 25).
8. Fase 6 — Pintat de tiles (DONE)
Introdueix la mecànica central: el jugador deixa rastre al terra en trepitjar-lo, i els sarcòfags compten tiles pintats (no tiles trepitjats). Font única de veritat del pintat: una nova capa PaintLayer escolta el moviment del Player, filtra pintabilitat i re-publica tile_painted, que és el que ara observen els sarcs.
Cadena d'events resultant
Player canvi de tile
→ bus: on_tile_entered { col, row, paint_tile_index }
→ PaintLayer: filtra Map::isPaintable + "primer pinta guanya"
→ buffer ARGB + bus: tile_painted { col, row }
→ EntityManager fan-out → Sarcophagus::notifyTilePainted
→ perímetre complet → OPENING → OPEN → sarcophagus_opened
→ tots oberts → Door::open
Dos events encadenats perquè el pintat no és la presència: es filtra per pintabilitat i per "ja pintat", i el re-publish garanteix que els sarcs no vegin falsos positius (p. ex. el jugador lliscant contra un mur, o un tile ja marcat per l'altre jugador).
Canvis per fitxer
| Path | Canvi |
|---|---|
| event_bus.hpp | Nou struct PaintedTile { col, row, tile_index } al Payload variant + overload publish. |
| map.hpp / .cpp | Map::isPaintable(col, row) → true si el tile cau dins del playfield i no s'encavalca cap sarc. |
| map/paint_layer.hpp / .cpp | Classe nova. Subscriu on_tile_entered, marca painted_[row][col], blita el tile al buffer ARGB propi, publica tile_painted. render() anàleg a Tilemap::render. |
| player.hpp / .cpp | Camp paint_tile_index_ al ctor (default -1). El Player publica PaintedTile amb el seu índex en lloc de TilePos. |
| entity.hpp / .cpp | Rename notifyTileEntered → notifyTilePainted. |
| concrete/sarcophagus.hpp / .cpp | Mateix rename. La lògica del visited_ és idèntica — només canvia qui li truca el hook. |
| entity_manager.hpp / .cpp | La subscripció canvia d'on_tile_entered → tile_painted. Fan-out renombrat a notifyPlayerTilePainted. |
| game_scene.hpp / .cpp | Membre std::unique_ptr<PaintLayer> paint_layer_ (declarat després d'entity_manager_ per garantir ordre invers de destrucció). Helper local readOptionalInt llegeix player1_paint_tile del YAML del mapa i el passa al Player. render() crida paint_layer_->render() entre tilemap_.render() i els actors; s'amaga al mode editor. |
| data/maps/pyramid.yaml | Camps nous player1_paint_tile: 2 i player2_paint_tile: 2 (índex provisional, canviable sense recompilar). |
| tests/test_entities_smoke.cpp | T9 adaptat: publica tile_painted directament al bus per aïllar el fan-out d'EntityManager de la resta de la cadena. |
Decisions de disseny
- Centralitzat vs per-sarc: l'estat "pintat" viu al
PaintLayer, no dins cada sarc. Els sarcs només apunten els tiles del seu perímetre que han estat notificats pintats. Aquesta separació deixa preparat per a items-en-sarc (obrir quanNitems trobats, no quan tots oberts) i per a altres productors de pintat futurs (enemics, trampes). - "Primer pinta guanya":
PaintLayer::tryPaintretorna false si el tile ja està pintat. No se sobreescriu amb l'índex d'un altre jugador. Reduïm fonts de decisió inconsistent; el rastre visual és estable. - Paint tile per jugador: l'índex l'aporta el YAML del mapa (
player1_paint_tile,player2_paint_tile), no el YAML del jugador. Raó: depèn del tileset del mapa — cada nivell pot oferir rastres diferents; canviar de mapa canvia l'índex vàlid. - Render al mode editor: desactivat. L'editor edita el tilemap base; la capa de pintat és d'estat de joc, no de dades del nivell. Quan
editor_.active(),SKIP_ACTORS=truei elpaint_layer_->render()se salta amb la resta d'actors. - Resolució: 16×16 reutilitzant
Map::tile_sizei l'origen del tilemap. ElPaintLayeremmagatzematile_size_com a camp propi per facilitar una eventual migració a 8×8 (graella 2× més fina) sense tocar la interfície. - Persistència: runtime only. No hi ha camp
painted:al YAML. Entrar alGameScenereseteja el pintat. - No cal
is_blockedalsarc->place(...)per a aquest nivell: l'outer_corridordelpyramid.yamlés exactament 1 tile, cosa que garanteix que el perímetre de qualsevol sarc (fins i tot els de cantonades) cau íntegrament dins del playfield i és pintable. Quedaria rellevant si el disseny futur posa sarcs sense passadís al voltant — cobert per PER-01.
Verificació
make debugnet, sense warnings ni de clang-tidy ni de cppcheck als fitxers nous../build/test_entities_smoke: ALL OK (14/14). T9 adaptat (publicatile_painteden lloc d'on_tile_entered); la resta de tests del sistema d'entitats passen sense tocar.timeout 5 xvfb-run ./build/aee_2026: arrenca, logPaintLayer: 21x15 tiles (336x240 px), tileset data/tilesets/tomb.gifdesprés deEntityManager: 27 entitats carregades, surt ambBye!sense crash.
Pendent derivat
| ID | Descripció | Estat |
|---|---|---|
| PAINT-01 | Tile de pintat definitiu quan l'art estigui madur. Ara 2 provisional al pyramid.yaml. Canviar l'índex no demana recompilar. |
DEFERRED |
| PAINT-02 | Resolució 8×8 si es vol més granularitat. El PaintLayer porta el seu propi tile_size_; cal propagar-lo al ctor per a no lligar-lo a Tilemap::getTileSize(). També cal decidir com es mapen els perímetres dels sarcs a sub-caselles. |
DEFERRED |
| PAINT-03 | Smoke unit del PaintLayer: "primer pinta guanya", filtre d'isPaintable, publicació de tile_painted. Ara la cadena està coberta end-to-end via T9 (fan-out del bus) i via l'smoke d'arrencada, però el filtre i la deduplicació no tenen test directe. |
DEFERRED |
| PAINT-04 | Player 2 cablejat al GameScene: la lectura del YAML i el ctor ja suporten un segon jugador amb paint_tile_index_ propi; només cal instanciar-lo amb Controls distints (p. ex. WASD) i afegir-lo al vector. |
DEFERRED |
Memòria durable afectada
INF-02 queda parcialment cobert: el PaintedTile ja porta tile_index (per jugador), que és una forma d'identificar qui pinta sense haver de propagar un player_id explícit. Si en el futur cal saber qui (p. ex. per puntuació individual), caldrà afegir el camp; el canvi és local al struct.
9. Estat global
- Andamiatge, integració, render animat, falsa perspectiva, moviment, animació direccional de momia, pintat de tiles: DONE. El
pyramid.yamlactual (4 torches, 20 sarcs, 1 door, 2 mummies) es carrega, anima, col·loca amb l'anchor correcte, col·lisiona segons caixa lògica, el z-order interleava per peus, la momia gira visualment a cada intersecció, i el jugador deixa rastre al terra. - Pendents majors: ENT-HAZ-01 (sistema de vides), ENT-DOOR-01 (transició d'escena), ENT-IA-02 (bias IA), PER-01 (perímetre amb blocked_predicate quan els sarcs siguin veïns), PAINT-03 (test unit del PaintLayer), PAINT-04 (Player 2 cablejat).
- Smoke tests: 14/14 passant; regla durable de cobrir amb smoke tota lògica no trivial respectada a totes les fases.
El document és viu: cada tasca nova hauria d'afegir-s'hi (entrada amb ID, descripció, estat) al fer-la o ajornar-la.