Un evento traumatico ha generato GHOST. GHOST ha paura e riconosce negli umani che visitano la stanza dei pericoli e come un animale reagisce ai movimenti bruschi. Ma se vi muovete lentamente e trovate il punto focale allora la memoria dell’evento si rivela. GHOST nasce, insieme ad Heatseeker, come lavoro sull’immaginario collettivo e personale, in particolare come rielaborazione delle tematiche di J.G.Ballard. La scelta di una installazione interattiva e quindi di strutture narrative non lineari è legata all’esplorazione di spazi che sono più psichici e mnemonici che storici. GHOST è un ambiente narrativo interattivo: narrativo in quanto prevede un percorso con un inizio, un centro dell’azione e una fine ed interattivo in quanto è la presenza è il movimento dell’interattore che permette lo svolgersi della narrazione. La stanza infatti è quieta se nessuno la esplora. GHOST infatti agisce come una sorta di memoria, che reagisce alla nostra esplorazione.

GHOST, nella versione è stata presentata durante il festival ToShare a Torino. In questa occasione lo spazio era una stanza costruita ad hoc di circa 3 metri per 4; le pareti erano alte circa 3. Un corridoio chiuso da una tenda conduceva all’interno. Nella stanza diversi sensori percepivano i movimenti e la posizione del visitatore. La minimale scenografia era composta da alcuni bidoni industriali, tracce di una civiltà del petrolio [d’acciaio]; il punto focale narrativo era il filmato mostrato in un monitor dentro uno dei bidoni. Una feritoia permetteva di osservare da fuori la stanza e chi vi si muoveva. I sensori erano dei comuni sensori da antifurto, modificati da Fabrizio Raimondo, in modo tale da essere collegati al digitalizzatore I-Cubex, che a sua volta codificava in uscita in formato MIDI. I segnali MIDI veniva poi letti da un software sviluppato ad hoc in Director MX eseguito su un titanium G4. Il portatile pilotava un paio di casse e il monitor nel bidone.

.

La stanza era pensata per un percorso individuale e la sensibilità dei sensori era tarata di conseguenza. I feedback di presenza e di movimento verso il visitatore erano di tipo sonoro e visivo. Il suono ricombinava, oltre a suoni ambientali e di station number, frammenti attribuiti ai dialoghi tra la torre di controllo e il pilota del volo UAFL93 che l’11 settembre 2001 si è schiantato sul pentagono. La frequenza con cui apparivano i suoni nella stanza è funzione dei movimenti del visitatore (più movimenti = più suoni).

La stanza percepiva i movimenti bruschi come aggressivi e cambiava suoni e frequenza di conseguenza. Dal fondo di uno dei bidoni d’acciaio proveniva della luce colorata e animata. Se ci si avvicinava abbastanza, si scopriva che in realtà la luce proveniva da un monitor. Il video partiva ma si interrompeva ad ogni movimento del visitatore, che era costretto a restare immobile per vederlo scorrere. Il filmato mostrava immagini di devastazione e messaggi testuali, poi al termine ordinava di uscire, mentre un sibilo ad alta frequenza riempiva la stanza fin quando non restava vuota . Naturalmente non tutti completavano il ciclo e qualcuno usciva prima di vedere ad esempio il video; altri entravano quando c’era già qualcuno, rendendo impossibile la visione.

.

L’idea è stata quella di dare alla stanza una sorta di ruolo attivo ed una intelligenza (sia pur semplificata) per eseguire la parte. GHOST è presentato quindi come una sorta di organismo abbastanza primitivo, tipo un insettoche risponde al modello “percezione-analisi-reazione”. La percezione era poco più che la raccolta dei dati sensoriali (per adesso i segnali degli IR e del tappetino a pressione). I segnali raccolti (codificati attraverso il MIDI) giungevano alla struttura di analisi di GHOST. In base allo stato interno e ai segnali provenienti dal mondo esterno GHOST decideva quali eventi fossero più significativi e se era il caso cambiava umore. Quindi la parte di reazione si incaricava appunto di reagire, ad esempio scegliendo quali suoni o immagini mostrare.

 

CODICE UTILIZZATO PER GHOST

Il codice che viene mostrato è in realtà pseudo codice Lingo tratto da quello originale di GHOST, ma per motivi di chiarezza è stato semplificato per mostrare il meccanismo; in GHOST ovviamente ci sono molti dettagli specifici in più.

Il ciclo principale di GHOST è :

on Main

— input from sensor and preprocessing
GHOSTSensorInput

— processing (internal/external status and FSM settings
GHOSTProcessing

— output
GHOSTScape

end Main

Code percezione GHOSTSensorInput (in buona parte tratto da Mark Coniglio)

Il codice si occupa semplicemente di vedere quale messaggio MIDI arrivi (attraverso l’ICUBEX) e di mapparlo su variabili interni. Nel codice vediamo ad esempio come reagisce all’arrivo di un messaggio NoteOn (ma lo stesso vale in generale). Le chiamate MIDI fanno riferimento all’XTRA XMIDI di Mark Coniglio, che su OSX ha funzionato senza problemi. Ad esempio se arriva un NoteOn 68 questo significa che qualcuno ha messo un piede sul sensore a tappeto.

on GHOSTSensorInput

message = getNextMessage(xm)
— debug only
MIDImessage = message

if (getAt(message, 1) <> -1) then — if there was a message
if (getAt(message, 1) = 144) then

set currentNoteOn to getAt(message,3)

case currentNoteOn of

“67” :

IRVideoBarrier = true

“68” :

carpetSensor = true

“69” :

IRPresenceTrigTime = the milliseconds

append(presenceTriggaList,IRPresenceTrigTime)

“71”:

IRMovement = true — a trigger movement has been detected

IRMoveTime = the milliSeconds


append(movaTriggaList,IRMoveTime)


end case

end if
end if

Code analisi GHOSTProcessing?

Per gestire gli umori di GHOST abbiano usato una soluzione classica: la state machine.

La GHOST state machine è appunto una semplice macchina a tre stati Nella nostra narrazione GHOST in genere è tranquillo (“QUIET”), ma se accade qualcosa si allarma (“FEAR”) e in casi particolari può anche diventare minaccioso (“MENACE”).

inserire grafico aggiornato state machine (vedi jpg allegato)

Per prima cosa occorre interpretare i dati sensoriali e vedere se è accaduto qualcosa di significativo (EVENT DETECTION).

on EventDetection
global IRVideoBarrier, oldIRVideoBarrier — IR video barrier output
global carpetSensor, oldCarpetSensor — carpet sensor output
global GateInfo,oldGateInfo — Gate sensors output
global IRMoveAlarm, oldIRMoveAlarm
global videoEnded — end of video trasmission
global videoZone, oldVideoZone — T if the human is in front of video
global GhostEvent

videoZone = (IRVideoBarrier or carpetSensor) — human in videoZone

GhostEvent = “” — reset event variable

— is somebody entering in the room?
if not oldGateInfo and GateInfo then — somebody is entering in the room! red alert!
GhostEvent = “humanJustInside”

else
if GateInfo then — somebody is in the room

if IRMoveAlarm and not oldIRMoveAlarm then — movement detected
GhostEvent = “moveDetected”

end if

if not IRMoveAlarm and oldIRMoveAlarm then — movement stopped
GhostEvent = “moveStopped”

end if

if videoZone and not oldVideoZone then — human just entered videoZone
GhostEvent = “humanJustInsideVideo”end if

if not videoZone and oldVideoZone then — human just exit from videoZone
GhostEvent = “humanLeftVideoZone”

end if

if videoEnded then — menace phase completed
GhostEvent = “endVideo”

end if
else

if oldGateInfo then
GhostEvent = “humanLeftRoom” — human left room

end if

end if

end if

oldGateInfo = GateInfo
oldIRMoveAlarm = IRMoveAlarm
oldIRVideoBarrier = IRVideoBarrier
oldCarpetSensor = carpetSensor
oldVideoZone = videoZone

end

In questa routine la variabile da tenere d’occhio è GhostEvent, i cui valori dovrebbero essere leggibili…ok qualche dettaglio non guasta. EventDetection legge i valori già interpretati che arrivano dai sensori: ad esempio carpetSensor indica semplicemente che qualcuno sta calpestando il tappetino sensore (che avevamo nascosto nella stanza). In base ai dati sensoriali GHOST decide quindi se è successo qualcosa di nuovo o di interessante.A seconda dello stato la stanza reagisce in modo ovviamente diverso, come un animale spaventato reagisce in modo diverso da uno tranquillo.
Esiste anche una sorta di gerarchia implicita nell’ordine con cui vengono esaminati i sensori: questo perché alcuni segnali possono essere molto più importanti di altri (immaginate ad esempio che sentiate il ruggito di un leone e che contemporaneamente sentiate un profumo di torta alle mele…quale dei due segnali dovreste esaminare prima? )

Inoltre più che il valore attuale di un sensore, è importante vedere se qualcosa è cambiato e per questo motivo memorizziamo i valori precedenti (ad esempio oldCarpetSensor)

Una volta che EventDetection è stata eseguita, chiamata da GHOSTProcessing, allora possiamo determinare quale sarà lo stato interno (umore) di GHOST.

Le variabili chiave sono currentFSMstatus e GhostEvent.
Nelle prime righe vediamo che se siamo in uno stato di quiete (currentFSMstatus = “quiet”) e GHOST si è accorto che qualcuno si muove nella stanza (GhostEvent = “moveDetected”) allora giustamente c’è da preoccuparsi (newGHOSTstatus = “fear”)!

Da notare anche che al momento della transizione viene comodo (anche se poco elegante, lo ammetto) resettare alcune variabili o comunque inserire tutte le eventuali inizializzazioni per la nuova fase.

on GHOSTProcessing

global currentFSMstatus — main room mood definition

global GhostEvent — event detected from sensors
global gateInfo, oldGateInfo — info from the IR Barriers : INSIDE,BETWEEN,OUTSIDE

global videoZoneStatus,videoZone

global carpetsensor, irvideobarrier — debug only

EventDetection — GhostEvent detection

———————————————–

— GHOST main FSM machine, define mood of the room
— from the current status and events define transition for the FSM
— if a new event has happened then a new related transition will be performed

newGHOSTStatus = “”

if currentFSMstatus = “quiet” then

if GhostEvent = “moveDetected” then
newGHOSTstatus = “fear”

sound(1).stop()

sound(1).volume = 0

else if GhostEvent = “humanJustInsideVideo” then
newGHOSTstatus = “menace”
sound(1).stop()

set sequenceStart to random(VideoticksLenght – 60*15) — reset movie pointer

end if

else

if currentFSMstatus = “fear” then

if not gateInfo then

newGHOSTStatus = “quiet”

oldVideoZone = false — reset video sensor, force to transition again

sound(1).stop()

else
if GhostEvent =”humanJustInsideVideo” then
newGHOSTStatus = “menace”

sound(1).stop()
sound(2).stop()
sound(3).stop()

end if

end if

else

if currentFSMstatus = “menace” then
if GhostEvent = “humanLeftRoom” then
member(10).fileName = “seq2.mov”

newGHOSTstatus =”quiet”
sound(3).stop()

else
if (not (videoZone) and videoZoneStatus <> “go away!”) then
member(10).fileName = “seq2.mov”

newGHOSTStatus = “fear”
sound(1).stop()
sound(2).stop()
sound(3).stop()
end if
end if

end if
end if
end if

if newGHOSTStatus <> “” then
currentFSMstatus = newGHOSTStatus
end if

end GHOSTProcessing

E’ chiaro che il mondo di GHOST è solo quello percepito attraverso i sensori, anche se nulla vieta di aumentare il numero e i tipi di sensore. Insieme esiste già una codifica implicita di cosa corrisponda ad un dato sensore. Ad esempio GHOST presuppone che carpetSensor sia posto davanti al video nel bidone.

Memoria in GHOST.
In apparenza la memoria di GHOST è determinata solo dalla macchina stati. In realtà questo non bastava, ad esempio, per capire se il visitatore si muoveva velocemente o meno.
Per alcuni sensori (ad esempio sotto per il sensore principale di movimento, un IR modificato) occorreva ricordarsi anche dei segnali passati.
Per prima cosa allora ogni segnale del sensore viene registrato come una tag in millisecondi, corrispondenti all’istante in cui il sistema l’ha percepito, in una lista, movaTriggaList. Poi occorre decidere un intervallo di tempo da esaminare. In questo caso la logica è che se negli ultimi due secondi ho avuto più di TriggaNumAlarm segnali, allora ho dei movimenti veloci. I sensori da noi usati hanno dei tempi di risposta sulla decina di millisecondi.
Dunque per questo sensore ho una “memoria” di 2000 millisecondi. Non sarebbe difficile aumentare questa memoria, ma al tempo stesso questo aumenta il tempo di reazione di GHOST a due secondi! Per dare un feedback allo user occorre stare sotto i 50-100 millisecondi; quindi lo stesso segnale viene elaborato in modi diversi, se serve cambiare l’umore o a dare feedback.

on IRMovementProcessing(triggaTime)

— IRMovement Data Processing
— any IRMovement is time stamped
— create a list movaTriggaList, who contains all the last triggers detected
— if a trigger is too old then delete it
— if in the last time interval are too many triggers then alarm!
— triggaTime is the time in milliseconds when the midi trigger was received

global movaTriggaList
global triggaInterval — interval of time to be examined (in milliseconds)
global triggaNumAlarm — threshold of number of trigger to generate an IRmoveAlarm
global IRMoveAlarm, IRVoiceAlarm, oldIRMoveAlarm

triggaInterval = 2000 — observation time interval in milliseconds
triggaNumAlarm = 2 — trigger number threshold to generate an alarm
triggaVoiceAlarm = 1 — trigger number threshold to play alarm voices

— the triggaNumAlarm and triggaInterval should be setted via tuning interface and saved via preference file
— tentative value triggaNumAlarm = 1 and triggaInterval = 100 milliseconds

nowTime = the milliseconds

triggaNum = count(movaTriggaList)

startInterval = nowTime – triggaInterval

repeat with i= 1 to count(movaTriggaList)
if (getat(movaTriggaList,i) < startInterval) then deleteAt(movaTriggaList, 1)
end repeat

oldIRMoveAlarm = IRMoveAlarm
oldIRVoiceAlarm = IRVoiceAlarm
IRMoveAlarm = (triggaNum >= triggaNumAlarm)
IRVoiceAlarm = (triggaNum>= triggaVoiceAlarm)

end

il mondo di GHOST è ricostruito sui dati sensoriali, ma ha già una struttura per l’aspettativa …legato di fatto ad una backstory, che è da scoprire, anche attraverso gli indizi del video.

In ultimo, esaminati i dati sensoriali e deciso come interpretarli e di che umore sarà GHOST, occorre decidere quali azioni eseguire.In questo caso saranno scelti dei suoni e delle immagini ad hoc per il visitatore. Il codice è nella routine GHOSTScape, che in funzione principalmente di currentFSMstatus decide di conseguenza.

reaction code

on GHOSTScape
— define the GHOST scape and other reactions, based on the main GHOST FSM status

global currentFSMstatus, oldCurrentFSMStatus
global gateInfo

global

case currentFSMStatus of
“quiet”:
GHOSTSound — slowly evolving little sounds, childish, carillons on channel 1

randomBackGround(69) — display randomly choosed images

sprite(15).visible = true

“fear”:
GHOSTSound
randomBackGround(69) — display randomly choosed images
sprite(15).visible = true

“menace”:

sprite(15).visible = false
GHOSTVideoMenace
GHOSTSound

end case

oldCurrentFSMStatus = currentFSMStatus

end GHOSTScape

Quello che conta in questa routine è che ad ogni stato (umore) di GHOST corrisponda un set diverso di reazioni, eventualmente con logiche completamente diverse.

A questo punto si inserisce di fatto lo user, che agendo nella stanza attiva dei nuovi segnali e il ciclo ricomincia. Ogni ciclo completo dura circa 50 millisecondi (grazie al G4) e basta per dare la sensazione di continuità dell’azione al visitatore.

Considerazioni finali sull’installazione:

Tecnicamente GHOST ha funzionato bene. Gli unici inconvenienti sono stati lo scollamento dalle pareti in un paio di occasioni dei sensori; altrimenti il sistema ha lavorato ininterrottamente per i cinque giorni del festival senza problemi.
Dal punto di vista del loop GHOST-visitatori , credo che nella maggior parte dei casi fosse evidente che la stanza reagisse e interagisse, meno evidente invece quali fossero le logiche interne.
Curioso come si attivasse un meccanismo di comunicazione tra i visitatori, quando si formava una coda in attesa davanti alla stanza.

Spero che questi semplici esempi diano una idea dell’approccio usato. La chiave di interpretazione è di avere scritto il codice intorno ad una metafora ed a avere strutturato per funzionalità.

Un’altra lezione l’abbiamo ricavata nella scelta della metodologia di progetto: dati i limiti di tempo e di risorse , il progetto è stato condotto usando l’approccio “extreme programming” (approccio che prevede lavoro in coppia, molta comunicazione, molto prototiping e testing, hw e sw per supportare lo sviluppo rapido e il testing). Il lavoro è stato codificato, testato e installato in meno di due settimane, preparando i sensori e scrivendo il codice da zero (il video e l’audio invece erano presi a prestito dal progetto HEATSEEKER ( HYPERLINK “http://www.heatseeker.it” www.heatseeker.it).

suggested bibliography
J.G.Ballard La mostra delle atrocità www.jgballard.com

Premier.Press.Data.Structures.For.Game.Programmers.pdf
OReilly.AI.for.Game.Developers.Jul.2004.eBook-DDU.chm
MIT.Press.Rules.of.Play.Game.Design.Fundamentals.chm
Wordware.Publishing.Programming.Game.AI.by.Example.eBook-LiB.chm

risorse
XMIDI di Mark Coniglio www.troikaranch.com

SHARE ONShare on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn